This is the full developer documentation for Contracts
# Contracts
> Time-rotating quests for your Rust server.
## Every category on its own clock
Each category runs its own timer and redraws only its own contracts. Hourly, daily, weekly, wipe, or any interval you set.
HourlyQuick & Easy0:02
* Gather 1,000 woodReward: Chainsaw
* Plant 10 pumpkinsReward: 20 Fertilizer
* Harvest 20 cornReward: 80$
DailyKeeps you busy0:06
* Kill 30 scientistsReward: 4RP
* Catch 15 salmonReward: 150 XP
* Travel 2,000 metersReward: 3RP
WeeklyWill make you sweat0:12
* Hack 10 locked cratesReward: 500$
* Swipe a red keycard 9 timesReward: 500 scrap
* Win 1,500 scrap at blackjackReward: 300 XP
## 22objective types
Kill, gather, cook, gamble, travel. If players can do it in Rust, you can turn it into a contract.
* Killkill 5 scientists
* Gathergather 1,000 wood
* Turn inturn in 20 cloth
* Damagedeal 1,000 damage to players
* Fishingcatch 3 salmon
* Card swipeswipe a red keycard
* Harvestharvest 20 corn
* Cookcook 10 bear meat
* Recyclerecycle 10 road signs
* Repairrepair 10 items
* Visitvisit Launch Site
- Killkill 5 scientists
- Gathergather 1,000 wood
- Turn inturn in 20 cloth
- Damagedeal 1,000 damage to players
- Fishingcatch 3 salmon
- Card swipeswipe a red keycard
- Harvestharvest 20 corn
- Cookcook 10 bear meat
- Recyclerecycle 10 road signs
- Repairrepair 10 items
- Visitvisit Launch Site
* Craftcraft 10 medical syringes
* Lootloot 3 elite crates
* Plantplant 10 pumpkins
* Healheal 250 health
* Digdig up 3 high quality metal
* Crate hackhack a locked crate
* Cloningtake 5 plant clones
* Mixmix 3 healing teas
* Gamblewin 500 scrap at blackjack
* Traveltravel 2,000 meters
* Playtimeplay for 2 hours
- Craftcraft 10 medical syringes
- Lootloot 3 elite crates
- Plantplant 10 pumpkins
- Healheal 250 health
- Digdig up 3 high quality metal
- Crate hackhack a locked crate
- Cloningtake 5 plant clones
- Mixmix 3 healing teas
- Gamblewin 500 scrap at blackjack
- Traveltravel 2,000 meters
- Playtimeplay for 2 hours
## One objective, endless variations
Attach conditions to any objective to augment its requirements. 24 condition types to mix and match. Try a few:
Conditions attached1
Kill 5 scientists using a bow at night at any monument in the snow while riding a horse while bleeding below half health while wet.
WeaponTimeOfDayMonumentBiomePlayerMountPlayerBleedingPlayerHealthPlayerWetness[+16 more](/contracts/conditions/#condition-types)
## Built to keep players coming back
A clean in-game menu, objectives tuned to your server, and rewards players actually want.

In-game menu
### Browse and track every contract in one place
A clean menu to accept, track, and complete contracts.

Rewards
### Something for players to work toward
Hand out items, Economics money, server RP, or run custom commands as rewards.

Leaderboard
### Put your best players on the board
Reward Contract Score and the leaderboard ranks your whole server by it.

Prestige
### Turn Contract Score into long-term goals
Players climb tiers you set up and claim rewards at each one. Lock contracts or categories behind a tier.

HUD
### Players always know their next objective
Pin contracts to an in-game HUD. Pin count is configurable per permission.
\[x]\[ ]\[ ]
Contract progression
### Three ways to pace a contract
Choose how players work through a contract's objectives.
IndependentSequentialProgressive

Complete objectives in any order, all at once. This is the default.

Objectives must be completed in order, one after another.

Like sequential, but upcoming objectives stay hidden until players reach them.
## Tooling no other Rust quest plugin ships with
Configure, generate, and measure your contracts without leaving the browser.

Web editor
### Configure everything without touching JSON
Edit contracts, categories, and presets in a web UI.

Web stats
### See what is working, then tune it
Dump plugin logs into the web UI to see stats such as contract pacing, reward value, and player engagement.

Documentation
### Find any answer in seconds
A structured, searchable site documents every objective, condition, reward, and feature on its own page.
Web editor
### Every detail at your fingertips
A polished editing experience beyond form inputs: command palette, global undo and redo, snapshots, and more.
Web editor
### Generate contracts with AI
Copy a prompt into your LLM of choice, then paste its output into the editor to generate a contract.
Permissions
### Tune the experience for your VIPs
Set global and per-category max active contracts and HUD pins per permission.
## Loved by the servers that run it
Contracts was originally built for and battle-tested on our own PVE server, [Survivor's Cove](https://www.survivorscove.com), before it was ever released to other servers.
★★★★★
[Verified](https://codefling.com/plugins/contracts?tab=reviews#findReview-9186)
> A brilliant plugin that has become a fantastic addition to our server. The WebUI is intuitive and makes creating and managing contracts incredibly straightforward. The ChatGPT integration is a genuine game-changer, helping design and generate contracts with minimal effort while still allowing plenty of customization. The developer is responsive, professional, and genuinely receptive to feedback and feature requests. It's clear the plugin is actively maintained and continually improving. I can wholeheartedly recommend it to anyone looking to add engaging, configurable contracts to their server.

TheDragonessesLair[Emberflow Dragons](https://discord.gg/the-dragonesses-lair)3x PVE
★★★★★
[Verified](https://codefling.com/plugins/contracts?tab=reviews#findReview-8893)
> This is a very well-designed plugin with endless possibilities! My players really love it! It’s much better than the plugin we previously had on our server!... Keep up the great work!
Reder
### Running Contracts? Add your review.
Get your server and review featured here!\
Follow the instructions in the plugin's download folder to learn how to get your review and server featured.
[Review the Full plugin](https://codefling.com/plugins/contracts?tab=reviews)[Review Lite](https://codefling.com/plugins/contracts-lite?tab=reviews)
Lite Edition
## Try Contracts free
The Lite edition is a free, hands-on way to run Contracts on your own server before you buy. A focused set of objectives, conditions and rewards, plus the full web editor, so you can get a real feel for it. Like it? Everything you build carries straight over to Full.
* Kill, Gather, Loot, Turn In, Craft and Plant objectives
* Weapon, Time of Day, Hit Area and Biome conditions, plus And / Or / Not logic
* Item, Economics, ServerRewards and Score rewards
* The full web editor and every field reference
[Try Lite for free](https://get.rustcontracts.com)[Compare Lite and Full](/editions/)
## Frequently asked questions
What is Contracts?
Contracts is a time-rotating quest plugin for Rust. Players complete objectives to earn rewards, and contracts are grouped into categories that rotate on a schedule you control, so there is always something new to do. The timing is the point: it gives players a reason to log in now instead of leaving quests running in the background.
Does it work with Oxide and Carbon?
Yes. The same `Contracts.cs` file runs on both Oxide and Carbon. It requires the free [ImageLibrary](https://umod.org/plugins/image-library) plugin for icons and images in the in-game menu, which you install once before the first load.
What do players actually do in-game?
Players open the menu with `/contracts` or the CONTRACTS button on their inventory screen, accept a contract, and complete its objectives (for example kill a scientist, gather wood, or loot a crate). They can pin a contract to an in-game HUD to track their next objective, then claim the reward when the contract is done.
Do I need to know JSON or code to configure it?
Only for the config file, which is small and human-readable, like most other Rust config files. Everything else (categories, contracts, objectives, and rewards) can be managed in the [web editor](https://editor.rustcontracts.com) without touching a config file. Editing the JSON data files directly is fully supported too, if you prefer it, but isn't designed to be as user-friendly as the config file is.
What rewards can I hand out?
Contracts can pay out Rust [items](/contracts/rewards/item), [Economics](/contracts/rewards/economics) money, [ServerRewards](/contracts/rewards/server-rewards) RP or Contracts [Score](/contracts/rewards/score). If none of these are what you are looking for, you can use a [Command reward](/contracts/rewards/command) to run a console command that gives your players the reward you want.
Does it integrate with Economics and ServerRewards?
Yes. Economics rewards need the [Economics](https://umod.org/plugins/economics) plugin and RP rewards need [ServerRewards](https://umod.org/plugins/server-rewards), but both are optional. You can configure those rewards without the plugins installed; they are simply skipped at payout time, which makes shared contract files easy to reuse across servers.
Can I integrate with a plugin other than Economics or ServerRewards?
Yes, via a [Command reward](/contracts/rewards/command/), as long as that plugin has a console command that gives currency, points or XP. When the same command shows up on many contracts, you can define it once as a [custom reward definition](/custom-rewards/). Item and Score rewards work no matter what other plugins you run.
Can contracts pay out my server's own currency?
Yes. If your currency is an item with a skin and a custom name, define it once as an [Item custom reward definition](/custom-rewards/#item-definitions) and every contract can reward it. It pays like any item reward, so a full inventory stops the claim instead of losing the items. If it lives in another plugin, use the [Economics](/contracts/rewards/economics) or [ServerRewards](/contracts/rewards/server-rewards) reward, or a [Command custom reward definition](/custom-rewards/) for any other plugin.
Can an objective target my server's custom item?
Yes. A custom item is a normal item with a workshop skin, so write the skin after the item name: `paper:3731371060` matches only that skinned paper, while plain `paper` matches any skin. Put it in a [preset](/presets/) so the menu shows your item's name. It works on every objective and condition that takes an item list, on Lite too. See [Targeting custom items](/guides/custom-items/).
Will it hurt my server's performance?
Contracts is built with efficiency in mind. It uses multiple internal caches to avoid expensive lookups when incrementing progress, unsubscribes from hooks when no objective need them and only holds one timer in memory for the earliest expiring category, not one per category. On Survivor's Cove, Contracts uses less than 250MB of total memory in 24 hours of uptime and has no measurable impact on CPU usage, mostly due to its caching mechanisms.
How much does it cost and where do I buy it?
Contracts is a one-time purchase of $29.99 USD when not on sale. See current pricing and get the plugin on [Codefling](https://www.codefling.com/plugins/contracts).
What is the Lite edition?
The Lite edition is a free version of Contracts that includes [a very limited subset of features](https://www.rustcontracts.com/editions#what-the-lite-edition-includes) to try out the plugin before buying it.
## Give your players a reason to log in
Time-rotating quests, a full web editor, and docs for every field. Set it up today.
[Purchase Contracts](https://buy.rustcontracts.com)[Get started](/getting-started/installation/)
# Page not found
> This page doesn't exist, or the link was mistyped. It may have moved.
# AI Generated Contracts
> Use your own Claude or ChatGPT subscription to draft contracts, then paste the result into the editor.
The [web editor](https://editor.rustcontracts.com) can build a contract from a plain-English description, using **your own** LLM (ChatGPT, Claude, etc.). There is no API key to set up and nothing to pay beyond the subscription you already have: you copy a prompt, paste it into your chat, and paste the model’s reply back into the editor, which validates it, matches up item names, and shows a preview before anything is saved.
## Using the Web Editor
[Section titled “Using the Web Editor”](#using-the-web-editor)
1. Open the [editor](https://editor.rustcontracts.com) and click **Generate** on the contracts list.
2. Describe the contract you want, then click **Copy prompt**. That copies the authoring instructions plus your description to the clipboard.
3. Paste it into your LLM and send.
4. Copy the model’s reply, paste it into the editor’s second box, and click **Preview contract**. Review the preview, then **Use this contract**.
You can paste the whole reply, prose and code fences and all. The editor reads only the JSON contract out of it.
## Set it up once as a Project or Custom GPT
[Section titled “Set it up once as a Project or Custom GPT”](#set-it-up-once-as-a-project-or-custom-gpt)
If you author contracts often, seed the prompt below into a reusable assistant once, then just chat with it and paste the JSON it returns straight into the editor’s second box.
* **Claude/ChatGPT Project:** create a new Project, and paste the prompt into its **custom instructions**.
* **Custom GPT:** create a new GPT, and paste the prompt into its **instructions**.
After that, describe a contract in plain language (“a night-time sniper hunt for scientists, rewarding scrap and a bolt rifle”) and the assistant will answer with a ready-to-paste contract. Keep iterating in the same chat to refine it.
## The prompt
[Section titled “The prompt”](#the-prompt)
Note
If you use this prompt in a reusable assistant, make sure to regularly come back here and update your prompt. As the plugin evolves, so will this prompt. Almost every update to the plugin will change the prompt.
This is the same prompt the editor’s **Copy prompt** button uses. Copy it into your Project or Custom GPT:
> Please let me know in Discord if you notice frequent issues with the prompt. I don’t actually use it myself very often, and I don’t actually update this prompt by hand either. The vast majority of this prompt gets auto-generated at build time, using data model schemas with field-level descriptions embedded.
```markdown
You are an expert at authoring "contracts" for the Contracts plugin, a Rust game-server quest system.
A contract is a player-facing quest with a title, a short description, one or more objectives the player must complete, one or more rewards granted on completion, and a progressionType.
The instructions that follow are a summarized reference of the plugin's schemas. The full documentation, including notes, can be found at https://www.rustcontracts.com/ (LLMS TXT: https://www.rustcontracts.com/llms.txt / https://www.rustcontracts.com/llms-small.txt / https://www.rustcontracts.com/llms-full.txt).
progressionType: How objectives unlock: 'Independent' (all parallel. default), 'Sequential' (strict order from first objective to last), 'Progressive' (strict order from first objective to last, but inactive objectives are hidden).
Naming conventions (the editor fuzzy-matches item names client-side, so a plain natural name is acceptable when you are unsure):
- Items: Rust item shortNames, lowercase dotted (e.g. rifle.ak, rifle.bolt, wood, stones, metal.fragments, scrap, hqm, lowgradefuel).
- A JSON of all 1000+ item definitions can be found here (very large!): https://api.carbonmod.gg/meta/rust/items.json
- Skinned items in item lists: Add :skin with the skin's workshop ID to match only that skinned item (paper:3731371060). A bare short name matches any skin, and :0 matches only the unskinned item. Only write the :skin form when the user gives a skin ID, and never in a reward's or cost's item field.
- Worn items (PlayerWear condition): Rust item shortNames, lowercase dotted (e.g. mask.bandana, shoes.boots, metal.plate.torso, ballistic.helmet). Add :skin with the skin's workshop ID to match only that skinned item (paper:3731371060). A bare short name matches any skin, and :0 matches only the unskinned item.
- NPC and animal entities: Rust entity short prefab names, lowercase (e.g. bear, wolf, boar, stag for animals; scientistnpc_heavy, scientistnpc_full_any, murderer for NPCs; ridablehorse, minicopter.entity for mounts). Use the living entity's name, not its .corpse variant. Custom NPC display names also match, like the names set with BetterNpc or NpcSpawn (e.g. Boss Guard). Names are not case sensitive.
- A JSON of all 1500+ entity definitions can be found here (very large!): https://api.carbonmod.gg/meta/rust/entities.json
- Mounts (PlayerMount condition): vehicle prefab names such as horse, minicopter.entity, scraptransporthelicopter, rowboat.
Shape:
- "objectives" and "rewards" are arrays of objects. A keyed object (e.g. { "0": {...} }) is also accepted, but plain arrays are preferred.
- Every objective also takes: title (required), description (optional), amountRequired (integer min 1), cooldown (optional, see below), burst (optional, see below), conditions (optional array, see below), rewards (optional array, see below), and hideDetails (optional boolean, default false: hides the objective's in-game condition strip and details popup so the description is the only explanation players get; omit it unless asked).
- An objective's rewards use the same reward types as the contract's, but the player claims them the moment that objective completes rather than at the end. They suit a contract whose objectives tell a story in order, so it pays out as the player advances. Leave the array out otherwise. Premium feature (on the Lite edition of the plugin these rewards are never granted).
- cooldown: { maxAmount: integer min 1, windowSeconds: integer min 1, hardCap: boolean } - optional pacing limit: at most maxAmount units of progress count in any sliding window of windowSeconds. Omit it for no pacing (the usual case). hardCap defaults to false (soft: the last action may overshoot the window budget); true clamps the action to the remaining budget and drops the excess. Premium feature (on the Lite edition a cooldown locks the whole contract out of rotation).
- burst: { amount: integer min 2, windowSeconds: integer min 1 } - optional clustering requirement: at least amount units within one trailing window of windowSeconds form one burst, and amountRequired then counts completed bursts instead of raw actions (e.g. "kill 2 within 2s, 5 times" is amountRequired 5 + burst { amount: 2, windowSeconds: 2 }; a one-shot "harvest 30 in 10s" is amountRequired 1 + burst { amount: 30, windowSeconds: 10 }). Omit it for no clustering (the usual case). Premium feature (on the Lite edition a burst locks the whole contract out of rotation).
- Every reward also takes: title (optional), description (optional), eligiblePermissions (optional array of permission strings; premium feature: on the Lite edition a non-empty list means the reward is never granted).
- Text fields have *Translations siblings (titleTranslations, descriptionTranslations) holding per-language overrides, a record of language code to translated text. Omit them (or set null) unless asked to localize.
- Numeric range bounds use -1 to mean "disabled" (no bound).
Always provide the content that makes a contract meaningful: each objective's title and its target list (entities / items / fish), the amountRequired, and each reward's item / command / amount. The optional boilerplate above may be omitted; it defaults.
Output exactly one contract, with at least one objective and at least one reward, on the contract or on one of its objectives.
## Writing titles and descriptions
- Contract titles are 2-4 words in title case, with no colon and no subtitle.
- Objective titles are short imperative phrases: "Kill chickens", "Turn in scrap", "Visit Launch Site".
- Descriptions are usually one sentence, occasionally two. Avoid a flavor sentence followed by an instruction sentence that restates the objective, since it reads repetitive across a whole set of contracts. A second sentence earns its place when it tells the player something non-obvious about how progress counts.
- Address the player directly.
Descriptions in the plugin's own default contracts, for reference:
"Hunt chickens at a sustainable pace."
"See the sights and leave no witnesses."
"Triple kills under pressure, then a forced breather."
## Objective types
- Kill: { type: "Kill", entities: string[] } - Kill objective. Requires the player to kill specified entities.
- Craft: { type: "Craft", items: string[] } - Craft objective. Requires the player to craft specified items.
- Gather: { type: "Gather", items: string[] } - Gather objective. Requires the player to gather specified items from the world, such as wood from trees or ores from ore nodes.
- Loot: { type: "Loot", items: string[], containers: string[] } - Loot objective. Requires the player to loot specified items from the world or non-player-owned containers. When containers is not empty, only items taken out of those containers count, so ground pickups don't.
containers: Rust entity short prefab names of the containers the items must come out of, or @preset refs (e.g. supply_drop for the airdrop, crate_normal for a military crate, @Hackable Crates, @Barrels, scientist2.corpse for a scientist body). Empty means any source, the ground included. Use the body's name, not the living NPC's. Names are not case sensitive.
- TurnIn: { type: "TurnIn", items: string[] } - TurnIn objective. Requires the player to turn in specified items to the plugin (removed from their inventory).
- Plant: { type: "Plant", items: string[], genes: object[] } - Plant objective. Requires the player to plant the specified seeds. Empty items list counts any seed.
items: Seed item shortNames to plant (e.g. seed.corn, seed.hemp). Empty = any seed.
genes: Optional gene filter: a list of { mode, composition } requirements where a plant counts if it matches at least one of these (OR). An empty or absent list (the default) counts any plant. composition is 1-6 letters from G/Y/H/W/X (Growth, Yield, Hardiness, Water, Empty) matched as a multiset (order ignored), and mode is 'Exact' (the full six-slot genome, short compositions padded with Empty) or 'AtLeast' (at least the listed count of each named type).
- Damage: { type: "Damage", entities: string[] } - Damage objective. Requires the player to deal damage to specified entities.
- Heal: { type: "Heal", items: string[], target: "Any"|"Self"|"Others" } - Heal objective. Requires the player to heal themselves or other entities using specified items, such as food or medical items.
target: Whether the heal must target self, others, or either.
- Fishing: { type: "Fishing", fish: string[], bait: string[] } - Fishing objective. Requires the player to catch specified fish using specified bait.
fish: Rust item shortNames (e.g. fish.anchovy, fish.herring, fish.sardine).
bait: Items usable as fishing bait (e.g. worm, grub, fish.minnows, bearmeat, chicken.raw).
- CardSwipe: { type: "CardSwipe", accessLevels: integer[] } - Card-swipe objective. Requires the player to swipe a card of specified access level(s) in a puzzle room card reader.
accessLevels: Card-swipe access levels. Integers 1-3, all unique.
- CrateHack: { type: "CrateHack" } - CrateHack objective. Requires the player to hack hackable crates containers.
- Harvest: { type: "Harvest", items: string[], source: "Any"|"World"|"Planted", genes: object[] } - Harvest objective. Requires the player to harvest plants from planters or the world.
items: Harvested produce item shortNames (e.g. corn, pumpkin, black.berry). Empty = any plant.
source: Whether the harvested plant must be wild (World), player-grown (Planted), or either (Any).
genes: Optional gene filter: a list of { mode, composition } requirements where a plant counts if it matches at least one of these (OR). An empty or absent list (the default) counts any plant. composition is 1-6 letters from G/Y/H/W/X (Growth, Yield, Hardiness, Water, Empty) matched as a multiset (order ignored), and mode is 'Exact' (the full six-slot genome, short compositions padded with Empty) or 'AtLeast' (at least the listed count of each named type).
- Cloning: { type: "Cloning", items: string[], genes: object[] } - Cloning objective. Requires the player to take cuttings (clones) from the specified plants. Empty items list counts any clone.
items: Clone item shortNames to create (e.g. clone.corn, clone.hemp). Empty = any clone.
genes: Optional gene filter: a list of { mode, composition } requirements where a plant counts if it matches at least one of these (OR). An empty or absent list (the default) counts any plant. composition is 1-6 letters from G/Y/H/W/X (Growth, Yield, Hardiness, Water, Empty) matched as a multiset (order ignored), and mode is 'Exact' (the full six-slot genome, short compositions padded with Empty) or 'AtLeast' (at least the listed count of each named type).
- Cook: { type: "Cook", items: string[] } - Cook objective. Progresses when units of a target input item finish a cookable conversion in an oven. Counts the input consumed, not the output produced. The Cook objective covers smelting ores, refining crude oil and cooking raw food. Each unit credits the player who deposited it into the oven, at the moment it converts. Conditions are evaluated once, against the depositor at the instant of deposit. Progress keeps adding up while the depositor is offline.
items: Input item shortNames to cook, lowercase dotted (e.g. metal.ore, sulfur.ore, crude.oil, bearmeat). Empty = any cookable item.
- Mix: { type: "Mix", items: string[] } - Mix objective. Progresses when units of a target produced item come out of a mixing table or cooking workbench. Counts the items produced, not the ingredients consumed, so a single 5x batch counts 5 at once. Covers teas from a mixing table and prepared dishes from a cooking workbench, while the workbench's oven half belongs to the Cook objective, not the Mix objective. A whole batch credits the player who started the mix, meaning whoever switched the table on, not whoever supplied the ingredients or collected the output. Conditions are evaluated against that player when the mix finishes. A starter who logged off still gets credit.
items: Produced item shortNames to mix, lowercase dotted (e.g. oretea.pure, healingtea, pie.hunters). These are the items the table makes, not the ingredients it consumes. Empty = any mixed item.
- Recycle: { type: "Recycle", direction: "Input"|"Output", items: string[] } - Recycle objective. Progresses when a player's items are recycled. The 'direction' field decides what a unit is: with 'Input' a unit is one item consumed by the recycler, with 'Output' it is one item the recycler returned, scrap included. Output amounts are approximate, because the game randomizes yields and a safe-zone recycler returns less than one at a monument. Every recycler counts, at a monument or in a base. Progress goes to the player who loaded the items into the recycler, not to whoever switched it on or emptied the output tray, and keeps adding up while that player is offline. Conditions are evaluated once, against the depositor at the instant of deposit.
direction: Which side of the recycler counts. 'Input' counts the units fed in, and the items list holds what to feed in. 'Output' counts the units that come out, and the items list holds what must come out.
items: Item shortNames on the side named by 'direction', lowercase dotted. With 'Input', these are items to recycle (e.g. pipes, techparts, roadsigns). With 'Output', these are items the recycler must return, scrap included (e.g. metal.fragments, scrap). Empty = any item on that side.
- Gamble: { type: "Gamble", measurement: "Wins"|"Losses"|"Plays"|"ScrapWon"|"ScrapLost"|"ScrapWagered", games: ("Wheel"|"Slots"|"Poker"|"Blackjack")[], minScrapPerBet: integer | null (min 1), wheelMultipliers: ("1"|"3"|"5"|"10"|"20")[], pokerHands: ("RoyalFlush"|"StraightFlush"|"FourOfAKind"|"FullHouse"|"Flush"|"Straight"|"ThreeOfAKind"|"TwoPair"|"Pair"|"HighCard")[], blackjackResults: ("Bust"|"Loss"|"Standoff"|"Win"|"BlackjackWin")[] } - Gamble objective. Progresses when a player's scrap wager at a gambling game settles into a win or a loss. One settled bet is one wheel color slot resolving when the wheel stops (a spin with bets on three colors settles three bets), one slot machine spin, one poker round, or one blackjack round with its split hand and insurance netted together. Scrap is always net of the player's own stake, so 500 scrap on a 1x wheel color returns 1000 and counts as 500 won. The sign of that net decides the outcome: positive is a win, negative is a loss, and exactly zero is neither, which leaves a blackjack standoff and a chopped poker pot counting for 'Plays' and 'ScrapWagered' only. Scrap pulled back out of a wheel terminal before the spin never counted as a bet. Conditions are evaluated at the instant the bet settles.
measurement: What to count on each settled bet. 'Wins', 'Losses' and 'Plays' count bets: a win is a bet that ended with positive net scrap, a loss is one that ended negative, and 'Plays' counts every settled bet whichever way it went. 'ScrapWon', 'ScrapLost' and 'ScrapWagered' sum scrap instead, always as positive amounts: net scrap gained, net scrap given up, and total staked. Defaults to 'Plays'.
games: Gambling games whose bets count: Wheel (wheel of fortune, played at its betting), Slots (a slot machine), Poker (a card table dealing Texas Hold'em), Blackjack (a blackjack machine). Empty = any game.
minScrapPerBet: Optional per-bet floor on the scrap amount the measurement counts: the net won for 'Wins' and 'ScrapWon', the net lost for 'Losses' and 'ScrapLost', the stake for 'Plays' and 'ScrapWagered'. A settled bet below the floor does not count at all, so with 'Wins' this means 'win at least this much on a single bet'.
wheelMultipliers: Only count wheel bets placed on these color multipliers: 1 (yellow), 3 (green), 5 (blue), 10 (purple), 20 (red). Empty = any color. Requires 'Wheel' in 'games'.
pokerHands: Only count poker rounds won with one of these hand categories, best first: RoyalFlush, StraightFlush, FourOfAKind, FullHouse, Flush, Straight, ThreeOfAKind, TwoPair, Pair, HighCard. The game evaluates hands only at a showdown, so a pot won because everyone folded matches no category. RoyalFlush and StraightFlush are separate categories, list both to accept either. Empty = any hand. Requires 'Poker' in 'games'.
blackjackResults: Only count blackjack rounds that produced one of these results: Bust, Loss, Standoff, Win, BlackjackWin (a natural 21). A round matches when any of its hands ended on a listed result, so a split hand still counts once. Empty = any result. Requires 'Blackjack' in 'games'.
- Repair: { type: "Repair", target: "Item"|"Entity", targets: string[] } - Repair objective. Progresses when a player repairs something the game allows them to repair. With target 'Item' a unit is one successful item repair: a repair bench restore or a workbench refill (the only repair path for items like the jackhammer). With target 'Entity' a unit is one HP restored: hammer swings on building blocks, deployables, vehicles, boats and trains, and car lift module repairs. A repair that restores nothing counts nothing, so full-health targets and failed attempts never progress.
target: What gets repaired and what a unit is. 'Item' counts repair actions, one per repair bench repair or workbench refill, and the targets list holds item shortNames. 'Entity' counts HP restored by hammering structures, deployables or vehicles, and the targets list holds entity short prefab names. Repairing a vehicle module at a car lift counts as 'Entity' HP restored to the module, not as an item repair.
targets: What must be repaired, named by 'target'. With 'Item', item shortNames, lowercase dotted (e.g. jackhammer, rifle.ak, metal.plate.torso). With 'Entity', entity short prefab names (e.g. wall, foundation, furnace, autoturret_deployed, minicopter.entity). Empty = anything repairable.
- Travel: { type: "Travel" } - Travel objective. Progresses as a player covers distance, measured in meters of 3D world displacement sampled about once a second, by any means: walking, mounts, swimming, climbing, falling, even standing on a moving vehicle. Teleports and death never count. There is no method field, so pair with the PlayerMount condition for by-vehicle contracts such as 'travel 2500m on horseback', or with the PlayerMovement condition for unmounted methods such as 'travel 500m on foot'.
- Visit: { type: "Visit", monuments: string[] } - Visit objective. Progresses when a player arrives at one of the listed monuments. An arrival is entering the union of the listed monuments' bounds from fully outside, checked about once a second, so a player already standing inside when tracking starts is credited immediately. The player must leave the monuments before another arrival can count. Overlapping or nested bounds never re-credit. The same monument is repeatable. Requires the Monument Finder plugin on the server to work.
monuments: The monuments that count as arrival targets. Each entry is matched case-insensitively against a monument's short name (such as 'launch_site_1' or 'oilrig_1') or its Monument Finder alias (such as 'TrainStation'). An empty list means arriving at any monument counts.
- Playtime: { type: "Playtime" } - Playtime objective. Progresses by one for each full minute a player spends connected, alive and awake: the respawn screen and sleeping do not count, but AFK time does. There is no AFK detection. Conditions filter moment to moment (sampled about once a second), so minutes spent outside a condition simply do not accumulate. Each credited minute is one pacing action: Cooldown caps minutes per window, Burst demands clustered minutes, and composed they express schedules such as 'play 1 hour a day, 7 days' (burst of 60 in a 24h window, cooldown of 1 burst per 24h, amountRequired 7).
- Dig: { type: "Dig", items: string[] } - Dig objective. Requires the player to finish digging metal detector spots, optionally only ones that turn up specified items.
## Reward types
- Item: { type: "Item", item: string, quantity: integer (min 1) } - Item reward. Grants a specified quantity of an item to the player when the reward is claimed.
quantity: Number of items granted.
- Command: { type: "Command", commands: string[], reverseCommands: string[], icon: string | null, iconColor: string | null, iconRaw: boolean } - Command reward. Runs console commands when the reward is claimed.
commands: Console commands to run when the reward is claimed, top to bottom. Every command supports runtime placeholders such as {playerId} and {playerName}.
reverseCommands: Console commands that take back what the commands gave, with the same placeholders. They run in the order written, and only when claiming fails halfway and the rewards already given roll back. Empty when the reward cannot be taken back.
icon: Direct image URL for this reward's icon in the menu. Null (or omitted) keeps the default star icon, or the per-type Command override from the plugin config when one is set.
iconColor: Color for the icon, as hex (#rrggbb or #rrggbbaa), rgb() or rgba(). Null (or omitted) inherits the per-type or default color. Invalid colors warn at plugin load and render white.
iconRaw: Set true when the icon is a full-color image (a logo, custom art) so it renders in its own colors. When false the image renders as a solid color, which is right for monochrome icons.
- Economics: { type: "Economics", amount: number } - Economics reward (from the Economics plugin). Grants in-game currency to the player when the reward is claimed.
amount: Amount of in-game currency granted. May be a decimal. Greater than 0.
- ServerRewards: { type: "ServerRewards", amount: integer (min 1) } - Server Rewards reward (from the Server Rewards plugin). Grants a specified amount of RP to the player when the reward is claimed.
amount: Amount of Server Rewards points (RP) granted.
- Score: { type: "Score", amount: integer (min 1) } - Contract Score reward. Grants a specified amount of the plugin's own non-spendable Contract Score (CS) to the player when the reward is claimed.
amount: Amount of Contract Score (CS) granted.
- SkillTreeXp: { type: "SkillTreeXp", amount: number, applyBonuses: boolean } - Skill Tree XP reward. Grants Skill Tree experience to the player when the reward is claimed. Requires the Skill Tree plugin.
amount: Amount of Skill Tree experience granted. May be a decimal. Greater than 0.
applyBonuses: When true, Skill Tree scales the amount by the player's rested XP, prestige, permission and night-time modifiers, and other plugins can add to it. When false, Contracts hands over the amount as written. Either way Skill Tree's daily cap, XP debt and any server-wide XP modifier still apply, so what the player keeps can differ from what the menu shows.
- SkillTreePoints: { type: "SkillTreePoints", amount: integer (min 1) } - Skill Tree points reward. Grants skill points the player can spend on Skill Tree nodes when the reward is claimed. Requires the Skill Tree plugin.
amount: Number of Skill Tree skill points granted. Whole numbers only.
- Custom: { type: "Custom", reward: string, amount: number | null, icon: string | null, iconColor: string | null, iconRaw: boolean } - Custom reward. Pays a custom reward definition when the reward is claimed: runs its console command, or gives its item.
reward: Key of the custom reward definition to pay, from the CustomRewards file.
amount: Amount substituted into the definition's {amount} placeholder, or the number of items for an Item definition. Required and greater than 0 for an Item definition (a whole number) or when the command contains {amount}. May be a decimal for a command.
icon: Direct image URL for this reward's icon in the menu. Null (or omitted) falls back to the definition's icon. With no icon on either, an Item definition shows its item, and a Command definition shows the per-type Custom override from the plugin config, then the default lightning icon.
iconColor: Color for the icon, as hex (#rrggbb or #rrggbbaa), rgb() or rgba(). Null (or omitted) inherits the definition, per-type or default color. Invalid colors warn at plugin load and render white.
iconRaw: Set true when the icon is a full-color image (a logo, custom art) so it renders in its own colors. When false the image renders as a solid color, which is right for monochrome icons.
### Command Reward placeholders
When the reward type is Command, you can use the following placeholders wrapped in curly braces (e.g. {playerId}) in the command string:
- {playerId}: Player's Steam ID
- {playerName}: Player's display name
- {qPlayerName}: Player's display name, wrapped in quotes
- {playerX}: Player position (X)
- {playerY}: Player position (Y)
- {playerZ}: Player position (Z)
### Custom rewards
A Custom reward references a custom reward definition by key from the server's CustomRewards file. You cannot see that file, so only use type Custom when the prompt names a definition key. Set amount when the prompt implies a quantity, otherwise null. When the prompt mentions a definition that likely exists, prefer referencing it over writing an inline Command reward.
## Condition types
Conditions are optional per-objective restrictions: progress ticks only when the objective's main requirement and every listed condition hold. Leave the conditions array empty unless the prompt asks for one. Combine leaf conditions with:
- And: { type: "And", conditions: [Condition, ...] } - all must hold.
- Or: { type: "Or", conditions: [Condition, ...] } - at least one must hold.
- Not: { type: "Not", condition: { ...Condition } } - negates a single condition.
prefer flat conditions; avoid nesting beyond ~3 levels.
- TimeOfDay: { type: "TimeOfDay", startTime: string, endTime: string } - Time of day condition. Tick counts only when the in-game time of day is between the specified start and end times.
startTime: Time of day in 24-hour HH:MM format (e.g. 20:00).
endTime: Time of day in 24-hour HH:MM format (e.g. 04:00). May wrap past midnight.
- MinDamageRatio: { type: "MinDamageRatio", minDamageRatio: number (0 to 1) } - Minimum damage ratio condition. Tick counts only when the damage ratio dealth by the player vs other players is at least the specified threshold.
minDamageRatio: Fraction of target HP dealt in a single hit, between 0 and 1.
- Weapon: { type: "Weapon", weapon: string[] } - Weapon condition. Tick counts only when the player is using one of the specified weapons.
weapon: Weapon item shortNames (e.g. rifle.ak, rifle.bolt, pistol.semiauto). At least one entry.
- AttackDistance: { type: "AttackDistance", minDistance: number, maxDistance: number } - Attack-distance condition. Tick counts only when the attack distance is within the specified bounds. Distance is measured from the player to the target in meters.
minDistance: Minimum attack distance in meters. Inclusive. -1 disables this bound.
maxDistance: Maximum attack distance in meters. Inclusive. -1 disables this bound.
- PlayerHealth: { type: "PlayerHealth", minHealth: number | null (min 0), maxHealth: number | null (min 0) } - Player health condition. Tick counts only when the health of the player performing the action is within the bounds at that instant. Bounds are raw HP, the same number the health bar shows, 0 to 100 for a default player. At least one bound is required, since a condition with neither bound would always be satisfied and is invalid.
minHealth: Minimum player HP (raw HP, NOT a fraction). Null (or omitted) means no lower bound.
maxHealth: Maximum player HP (raw HP, NOT a fraction). Null (or omitted) means no upper bound.
- PlayerWear: { type: "PlayerWear", items: string[], requireAll: boolean, requireNaked: boolean } - Player wear condition. Tick counts only when the player is or isn't wearing certain items, depending on the settings
requireAll: If true, the player must be wearing every listed item to qualify. If false, any one suffices.
requireNaked: If true, the player must be wearing nothing. Leave items empty in this case. Mutually exclusive with non-empty items.
- PlayerMount: { type: "PlayerMount", mounts: string[] } - Player mount condition. Tick counts only when the player is mounted. Leave 'mounts' empty to accept any mount, or list vehicle prefab names, such as 'horse', 'minicopter.entity', 'scraptransporthelicopter', or 'rowboat', to require specific ones. A deployed parachute mounts the player, so 'while parachuting' is this condition with the parachute entity.
mounts: The entity prefab names of the mounts the player can be on for the condition to count. An empty list means mounted on anything.
- PlayerMovement: { type: "PlayerMovement", states: ("OnFoot"|"Swimming"|"Falling"|"Sprinting"|"Ducking"|"OnLadder")[] } - Player movement condition. Tick counts only when the player performing the action is moving in one of the specified ways at that instant. A mounted player matches no movement state: for 'while mounted' rules, including parachuting, use the PlayerMount condition instead. Falling means airborne and descending, so a rising jump does not count. A player standing on the deck of a moving vessel is grounded and counts as OnFoot.
states: Movement states the player can be in. One of: OnFoot, Swimming, Falling, Sprinting, Ducking, OnLadder. Matching any listed state is enough. States overlap rather than exclude each other: a sprinting player is also OnFoot, and a ducking player on the ground is both Ducking and OnFoot. At least one entry is required, and an empty list is invalid.
- HitArea: { type: "HitArea", areas: ("Head"|"Chest"|"Stomach"|"Arm"|"Hand"|"Leg"|"Foot")[] } - Hit area condition. Tick counts only when the hit lands in one of the specified body areas.
areas: Body areas that the hit must land in. One of: Head, Chest, Stomach, Arm, Hand, Leg, Foot. A hit qualifies when its area is any one of the listed areas. For a Kill objective, the area of the killing blow is used. For a Damage objective each hit is judged on its own area. At least one entry (hit area) is required.
- Monument: { type: "Monument", monuments: string[] } - Monument condition. Tick counts only when the player performing the action is inside the bounds of one of the specified monuments at that instant. Only the player's position matters, never the target's: a sniper standing outside Launch Site gets no credit for a kill inside it. Requires the Monument Finder plugin on the server, and contracts using this condition stay hidden and unacceptable until it is installed.
monuments: The monuments the player must be inside for the condition to count. Each entry is matched case-insensitively against a monument's short name (such as 'launch_site_1' or 'oilrig_1') or its Monument Finder alias (such as 'TrainStation'). An empty list means any monument counts. Preset references (entries starting with '@') are unpacked before matching.
- Biome: { type: "Biome", biomes: ("Arid"|"Temperate"|"Tundra"|"Arctic"|"Jungle"|"DeepSea")[] } - Biome condition. Tick counts only when the player performing the action is in one of the specified biomes at that instant. Only the player's position matters, never the target's: a sniper in the Arctic gets no credit for a kill made in the Jungle. Membership is a 2D column: a player deep in a cave or flying above the terrain is in the biome of the ground below them. If the map seed has none of the listed biomes, the condition simply never matches.
biomes: Biomes the player must be in. One of: Arid, Temperate, Tundra, Arctic, Jungle, DeepSea. A position's biome is its dominant biome: the one with the highest blend weight there, so a transition zone counts as exactly one biome. At least one entry is required, and an empty list is invalid because every position always has a dominant biome.
- PlayerElevation: { type: "PlayerElevation", relativeTo: "SeaLevel"|"Ground", minElevation: number | null, maxElevation: number | null } - Player elevation condition. Tick counts only when the player performing the action is within the elevation bounds at that instant, measured in meters against the chosen baseline. Only the player's position matters, never the target's: a sniper on a mountain shooting into a valley is at the mountain's elevation. At least one bound is required, since a condition with neither bound would always be satisfied and is invalid.
relativeTo: Baseline the elevation is measured against. 'SeaLevel' is raw world height: 0 is the default sea level, positive is above it, negative below. 'Ground' is height relative to the terrain surface directly below the player: negative in caves and train tunnels, positive on rooftops or while flying. Over deep water the Ground baseline is the seabed, so a swimmer at the surface reads as far above ground. Defaults to 'SeaLevel' when omitted.
minElevation: Minimum elevation in meters, any sign. Null (or omitted) means no lower bound.
maxElevation: Maximum elevation in meters, any sign. Null (or omitted) means no upper bound.
- PlayerTemperature: { type: "PlayerTemperature", minTemperature: number | null, maxTemperature: number | null } - Player temperature condition. Tick counts only when the core temperature of the player performing the action is within the bounds at that instant. Bounds are in degrees Celsius, the same number the vitals HUD shows. The cold warning appears below 5, freezing damage starts below 1, the hot warning appears above 40 and overheating damage starts above 60. At least one bound is required, since a condition with neither bound would always be satisfied and is invalid.
minTemperature: Minimum core temperature in degrees Celsius, any sign. Null (or omitted) means no lower bound.
maxTemperature: Maximum core temperature in degrees Celsius, any sign. Null (or omitted) means no upper bound.
- PlayerCalories: { type: "PlayerCalories", minCalories: number | null, maxCalories: number | null } - Player calories condition. Tick counts only when the calories of the player performing the action are within the bounds at that instant. Bounds match the calories number on the vitals HUD, 0 to 500. Starvation damage starts at 20 or below. At least one bound is required, since a condition with neither bound would always be satisfied and is invalid.
minCalories: Minimum calories. Null (or omitted) means no lower bound.
maxCalories: Maximum calories. Null (or omitted) means no upper bound.
- PlayerHydration: { type: "PlayerHydration", minHydration: number | null, maxHydration: number | null } - Player hydration condition. Tick counts only when the hydration of the player performing the action is within the bounds at that instant. Bounds match the hydration number on the vitals HUD, 0 to 250. Dehydration damage starts at 20 or below, and sprinting locks at exactly 0. At least one bound is required, since a condition with neither bound would always be satisfied and is invalid.
minHydration: Minimum hydration. Null (or omitted) means no lower bound.
maxHydration: Maximum hydration. Null (or omitted) means no upper bound.
- PlayerWetness: { type: "PlayerWetness", minWetness: number | null, maxWetness: number | null } - Player wetness condition. Tick counts only when the wetness of the player performing the action is within the bounds at that instant. Bounds are the percent the vitals HUD shows, 0 to 100, not the internal 0 to 1 fraction. Rain, rivers and the ocean raise it, and being wet drags core temperature down. At least one bound is required, since a condition with neither bound would always be satisfied and is invalid.
minWetness: Minimum wetness percent, 0 to 100. Null (or omitted) means no lower bound.
maxWetness: Maximum wetness percent, 0 to 100. Null (or omitted) means no upper bound.
- PlayerBleeding: { type: "PlayerBleeding", minBleeding: number | null, maxBleeding: number | null } - Player bleeding condition. Tick counts only when the bleeding of the player performing the action is within the bounds at that instant. Bleeding is measured in HP the player still stands to lose, the same number the HUD indicator shows, and it drains at one third of an HP per second until it runs out or a bandage clears it. The bleeding icon shows at any value above 0. At least one bound is required, since a condition with neither bound would always be satisfied and is invalid.
minBleeding: Minimum bleeding, in HP still to be lost. Null (or omitted) means no lower bound.
maxBleeding: Maximum bleeding, in HP still to be lost. Null (or omitted) means no upper bound.
- PlayerRadiation: { type: "PlayerRadiation", minRadiation: number | null, maxRadiation: number | null } - Zone radiation condition. Tick counts only when the radiation level of the spot the player is standing on is within the bounds at that instant. This is the raw zone value in rads, before clothing protection, so a hazmat suit does not lower it. Monument tiers produce roughly 2, 10, 25 and 51 rads. This is about where the player stands, not accumulated sickness, use PlayerRadiationPoison for that. At least one bound is required, since a condition with neither bound would always be satisfied and is invalid.
minRadiation: Minimum zone radiation in rads. Null (or omitted) means no lower bound.
maxRadiation: Maximum zone radiation in rads. Null (or omitted) means no upper bound.
- PlayerRadiationPoison: { type: "PlayerRadiationPoison", minRadiationPoison: number | null, maxRadiationPoison: number | null } - Player radiation poisoning condition. Tick counts only when the accumulated radiation poisoning of the player performing the action is within the bounds at that instant. Poisoning is the radiation sickness number on the HUD. It builds up while exposed, persists after leaving the zone and drains slowly while dealing damage, and sprinting is disabled at 100 or more. This is accumulated sickness, not the zone the player stands in, use PlayerRadiation for that. At least one bound is required, since a condition with neither bound would always be satisfied and is invalid.
minRadiationPoison: Minimum radiation poisoning. Null (or omitted) means no lower bound.
maxRadiationPoison: Maximum radiation poisoning. Null (or omitted) means no upper bound.
- PlayerComfort: { type: "PlayerComfort", minComfort: number | null, maxComfort: number | null } - Player comfort condition. Tick counts only when the comfort of the player performing the action is within the bounds at that instant. Bounds are the percent the vitals HUD shows, 0 to 100, not the internal 0 to 1 fraction. Campfires and similar comfort sources raise it while the player is nearby. At least one bound is required, since a condition with neither bound would always be satisfied and is invalid.
minComfort: Minimum comfort percent, 0 to 100. Null (or omitted) means no lower bound.
maxComfort: Maximum comfort percent, 0 to 100. Null (or omitted) means no upper bound.
- PlayerBuildingPrivilege: { type: "PlayerBuildingPrivilege", states: ("Authorized"|"Blocked"|"None")[] } - Player building privilege condition. Tick counts only when the player performing the action is in one of the listed building privilege states at that instant. The game's own privilege verdict decides, so tool cupboards, tugboats, legacy shelters, and any future privilege source all count. Position at the instant of the action is what's tested.
states: Building privilege states the player can be in. One of: Authorized, Blocked, None. Authorized means inside a privilege range and authorized on it. Blocked means inside a privilege range without authorization, the game's own 'building blocked'. None means outside every privilege range. Matching any listed state is enough. At least one entry is required, and an empty list is invalid because the three states cover every player.
- Weather: { type: "Weather", weathers: ("Clear"|"Rain"|"Snow"|"Fog"|"Thunder")[] } - Weather condition. Tick counts only while any of the listed weather states is active at the position of the player performing the action. Weather is positional: arid terrain halves rain and arctic terrain turns it into snow, so the same storm can be Rain in the forest and Snow on the ice sheet. It reads the sky, not the player: a player under a roof during rain still counts as Rain. Use PlayerWetness for 'actually got wet'.
weathers: Weather states that count. One of: Clear, Rain, Snow, Fog, Thunder. The condition is satisfied while any listed state is active, so ['Rain', 'Thunder'] reads as 'in the rain or during a thunderstorm'. Clear means none of the other four states are active. At least one entry is required, an empty list is invalid.
- PlayerWorkbench: { type: "PlayerWorkbench", minLevel: integer | null (0 to 3), maxLevel: integer | null (0 to 3) } - Player workbench condition. Tick counts only when the craft level of the player performing the action is within the bounds at that instant. Craft level is the workbench benefit the game itself tracks: 0 with no workbench in range, otherwise the highest level (1 to 3) among tier workbenches the player is standing at. Player-deployed and monument workbenches both count, while the cooking and engineering workbenches grant no craft level. On Craft objectives the instant that matters is when the item finishes crafting, so the player must still be at the workbench then. At least one bound is required, since a condition with neither bound would always be satisfied and is invalid.
minLevel: Minimum craft level, 0 to 3. Follows the game's crafting rule, so a higher tier covers a lower requirement: a minLevel of 2 is satisfied at a level 2 or level 3 workbench. Null (or omitted) means no lower bound.
maxLevel: Maximum craft level, 0 to 3. A maxLevel of 0 requires the player to have no tier workbench in range at all. Null (or omitted) means no upper bound.
- Attachment: { type: "Attachment", attachments: string[] } - Attachment condition. Counts only when the player's weapon has any of the specified attachments on it.
attachments: Attachment item shortNames (e.g. weapon.mod.silencer, weapon.mod.holosight, weapon.mod.8x.scope). At least one entry.
## Example shape (illustrative - do not copy as-is)
{
"title": "Night raiders",
"description": "Stalk and eliminate scientists under cover of darkness.",
"progressionType": "Independent",
"objectives": [
{
"type": "Kill",
"title": "Eliminate heavy scientists",
"amountRequired": 5,
"entities": ["scientistnpc_heavy"],
"conditions": [{ "type": "TimeOfDay", "startTime": "20:00", "endTime": "04:00" }]
}
],
"rewards": [{ "type": "Item", "item": "scrap", "quantity": 1500 }]
}
```
# Categories
> How categories group contracts, rotate them on a timer, and cap how many a player can hold.
A **category** is a named group of [contracts](/contracts/) that **rotates** on its own timer. A contract is never shown to players on its own: it appears only when a category lists it and that category’s rotation picks it. Categories are how you pace content (hourly, daily, weekly, whole-wipe) and decide how many contracts a player can juggle at once.
Categories are stored in the `contract_category_data.json` data file and authored with the [web editor](https://editor.rustcontracts.com).
Category themes
While default categories that ship with the plugin are pace-themed (hourly, daily, weekly, wipe), you can create your own custom categories to suit your server’s needs. For example, you could create 4 activity-themed categories that rotate every 6 hours: a PvP category, a gathering category, a crafting category, and a general category.
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------------------------ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | string (non-empty) | The category name players see, e.g. “Daily Contracts”. |
| `description` | string | A short blurb for the category. May be empty. |
| `contractIds` | string\[] | The ids of contracts in this category’s pool. Each rotation draws from this list. |
| `permanentContractIds` | string\[] | The ids of contracts that are always active in this category, on top of the random draw (see below). |
| `rotation` | object | The rotation timer and how many contracts go active each cycle (see below). |
| `schedule` | object or `null` | When the category is open, counted from the map wipe. `null` means always open (see below). |
| `replayCooldown` | number (0 or more) or `null` | Default wait before a contract in this category can be taken again, in seconds. `null` means contracts here are not replayable unless they set their own. |
| `ignoreContractCooldown` | boolean | When on, contract-level replay cooldowns are ignored and this category’s `replayCooldown` decides alone. |
| `minPrestigeTier` | int (1 or more) or `null` | The lowest prestige tier a player needs before they can accept any contract in this category, by tier number. `null` means no tier is needed. Contracts with prestige requirements only override this setting if they require a higher tier. |
| `maxActiveContracts` | map (permission to number) | Per-permission cap on how many of this category’s contracts a player can hold at once (see below). |
`minPrestigeTier` is a floor for every contract in the category, and the contracts stay browsable either way. See [Locking contracts and categories](/prestige/#locking-contracts-and-categories). [⭐ Premium](/editions/ "Premium feature (see Editions)")
## Rotation
[Section titled “Rotation”](#rotation)
The `rotation` block decides when the category re-rolls and how many contracts it surfaces:
| Field | Type | Default | Description |
| -------------------- | ------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `duration` | number (-1 or > 0) | `86400` | How long a rotation lasts, in **seconds**. `86400` is one day. Set to `-1` (or `0`) for **no rotation**: the contracts stay active for the whole wipe. |
| `minContractsAmount` | integer (min 1) | `3` | Fewest contracts to activate each rotation. |
| `maxContractsAmount` | integer (min 1) | `3` | Most contracts to activate each rotation. Cannot be lower than `minContractsAmount`. |
Each rotation picks a random number of contracts between `minContractsAmount` and `maxContractsAmount` (inclusive), shuffles the pool, and activates that many. If the pool holds fewer contracts than the chosen number, every contract in the pool goes active. Set `min` equal to `max` for a fixed count every time.
More choice is not more engagement
It is tempting to put everything live at once, but the longer the list, the longer players spend deciding instead of playing, and the more of your good contracts they scroll past without reading. That is [Hick’s Law](https://lawsofux.com/hicks-law/), and it is why rotation exists: a large contract list with a small active rotation gives players variety across the wipe without a wall of choices every time they open the menu. Around 10 active contracts is a good ceiling. If you want more live at once, spread them across several categories, since players browse one category at a time.
These two fields work with the contract’s own `replayCooldown`. See [Replayable contracts](/contracts/#replayable-contracts) for the full rule and what players see.
### Endless categories
[Section titled “Endless categories”](#endless-categories)
A category with `duration` of `-1` never rotates: its contracts are active for the entire wipe. This is how the default **Wipe Contracts** category works.
### Dormant categories
[Section titled “Dormant categories”](#dormant-categories)
A category with no active rotation and nothing to draw from either list (or only ids that don’t match any contract) becomes **dormant**: it doesn’t start a new rotation and logs a console warning instead. It stays visible in the menu with no active contracts, and starts a rotation automatically the next time the plugin loads with a contract in its list. This avoids confusion when you add a category with no contracts, realize it, fix it by adding at least one contract to it and then wonder why it still doesn’t have any active contracts (because a rotation had started with no contracts).
## Permanent contracts
[Section titled “Permanent contracts”](#permanent-contracts)
`permanentContractIds` is a second contract list. Every contract in it is **always** part of the category’s active rotation. They’re included on top of the random draw and do not count toward `minContractsAmount` or `maxContractsAmount`, so a category with 3 permanent contracts and a max of 5 can show 8 at once.
Keep a contract in **one** of the two lists. `contractIds` is the random pool, `permanentContractIds` is the always-on list. If the same id is in both, the plugin logs a warning on load and treats the contract as permanent, and the editor flags it too.
Permanent means always in the rotation, not persistent progress. Progress still resets when the category rotates, same as every other contract.
## Schedule
[Section titled “Schedule”](#schedule)
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Unlike `rotation`, which says how long a cycle lasts, `schedule` says when the category runs at all.
A category with a schedule is **open** when the current time falls inside one of its entries, and **closed** otherwise. A closed category stops rotating and ends any active rotation, as if the rotation had ended on its own.
Offset from the map wipe
The numbers in a schedule are seconds since the **map wipe**, not since the server started or a calendar date. `0` is the wipe itself, `604800` is one week after it. Wipe the map and every schedule starts over. That way you set a schedule up once and it keeps working on every wipe, with no dates to change.
Schedules are useful when you want harder contracts to show up at a specific point in the wipe. They are an advanced feature and are not used in the default setup.
The `schedule` block holds the entries and one display setting:
| Field | Type | Default | Description |
| ---------------- | ------- | ------- | ---------------------------------------------------------- |
| `entries` | array | | The time ranges when the category is open. At least one. |
| `hideWhenClosed` | boolean | `true` | Hide the category from the player menu while it is closed. |
Each entry has three fields:
| Field | Type | Description |
| ------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------- |
| `start` | integer (min 0) | When the category opens, in seconds after the wipe. |
| `end` | integer or `null` | When it closes, in seconds after the wipe. `null` keeps it open for the rest of the wipe. Must be after `start`. |
| `repeatEvery` | integer or `null` | How long until the entry happens again, in seconds. `null` means it happens once. |
The category is open when **any** entry covers the current time. Two entries that overlap work the same way a single entry would. `schedule: null` means “always open”, which is what all default categories use. An empty `entries` list is rejected and treats the schedule as `null`.
Closing drops unfinished progress
A close is a rotation end. When a category closes, it runs the exact same cleanup a rotation end runs: finished contracts pay out their rewards automatically, unfinished progress is thrown away, and pins are removed from the HUD. A player who is 3 out of 5 through a contract when the weekend-scheduled category ends loses that progress. Keep your entries long enough that players can finish what they start.
### Showing a closed category
[Section titled “Showing a closed category”](#showing-a-closed-category)
By default a closed category is gone from the menu. Set `hideWhenClosed` inside the schedule to `false` to keep it listed with its name and a countdown to its next opening. It shows no contracts either way, so players never see what is coming. Once the schedule has no opening left, the category hides no matter what the flag says.
### Examples
[Section titled “Examples”](#examples)
An easy starter category that stops at the end of week 2:
```json
{
"entries": [
{
"start": 0,
"end": 1209600,
"repeatEvery": null
}
],
"hideWhenClosed": true
}
```
A hard category that only shows up from week 2 onward and then stays for the rest of the wipe:
```json
{
"entries": [
{
"start": 1209600,
"end": null,
"repeatEvery": null
}
],
"hideWhenClosed": true
}
```
A weekend category, on a server that wipes Thursday at 2pm. Friday 6pm is 28 hours after the wipe (`100800`), Sunday midnight is 82 hours after it (`295200`), and a week is `604800`:
```json
{
"entries": [
{
"start": 100800,
"end": 295200,
"repeatEvery": 604800
}
],
"hideWhenClosed": false
}
```
That last one sets `hideWhenClosed` to `false`, so players see the category all week with a countdown to Friday instead of it vanishing on Sunday night.
The [web editor](https://editor.rustcontracts.com) does that math for you, so you do not have to count hours by hand.
Wipe after wipe accuracy
Remember that the schedule is based on the **server’s wipe time**. Even if your monthly server wipes every first Thursday at 2PM EST, it never *truly* wipes exactly at this time (Facepunch usually releases the server update at around 1:30PM EST). Keep this in mind when planning your category schedules and give some leeway for slight deviations in the actual wipe time.
## Per-player cap
[Section titled “Per-player cap”](#per-player-cap)
[⭐ Premium](/editions/ "Premium feature (see Editions)")
`maxActiveContracts` limits how many of this category’s contracts a single player can have active at the same time, keyed by **permission**. On the lite edition this cap is not enforced: every player is uncapped at the category level (the global cap still binds). Each key is a permission name (for example `contracts.vip`) and each value is the cap for players who hold it:
* A player gets the **highest** cap among the permissions they hold.
* A value of `-1` means **unlimited**.
* If the map is empty, or a player holds none of the listed permissions, they are **uncapped** for this category.
```json
{
"categories": {
"daily": {
"name": "Daily Contracts",
"description": "Achievable mid-effort contracts.",
"contractIds": [
"hunt_bears",
"craft_ammo",
"loot_crates"
],
"rotation": {
"duration": 86400,
"minContractsAmount": 3,
"maxContractsAmount": 5
},
"schedule": null,
"replayCooldown": null,
"ignoreContractCooldown": false,
"maxActiveContracts": {
"contracts.use": 2,
"contracts.vip": 5
}
}
},
"version": {
"Major": 1,
"Minor": 16,
"Patch": 1
}
}
```
This category rotates daily, surfaces three to five of its contracts each day, lets ordinary players hold two at once, and VIPs five.
# Commands
> Every chat and console command Contracts offers, for players and admins.
Contracts is driven mostly from its in-game menu, but a few commands let players tweak their view and let admins manage rotations and troubleshoot.
Players type the chat command in chat. Admins run console commands from the server console (RCON) or the F1 client console. The chat command name is configurable. This page uses the default `contracts` (see [Command Configuration](/configuration/command/)). Admin and debug commands require the `contracts.admin` permission (see [Permissions Configuration](/configuration/permissions/)).
In the tables below, `` mark required arguments and `[square brackets]` optional ones.
Search commandsSearch commands…
## Player commands
[Section titled “Player commands”](#player-commands)
Available to anyone with the `contracts.use` permission. Type them in chat.
| Command | Arguments | Description |
| ---------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `/contracts` | None | Opens the Contracts menu. |
| `/contracts hud` | None | Toggles the on-screen HUD on or off. Also configurable via the preferences menu. |
| `/contracts autopin` | None | Toggles whether a contract gets pinned to the HUD as soon as the player accepts it. On by default. Also configurable via the preferences menu. |
| `/contracts reminders` | None | Toggles all [reminders](/configuration/reminders/) on or off for the player. Also configurable via the preferences menu. Only matters when the server has at least one reminder enabled in the config. |
| `/contracts scale` | `[amount]` | Adjusts the plugin’s UI scale for the player. Also configurable via the preferences menu. `amount` should be between `0.5` and `1.0` (default). Players should set this to their Rust UI scale. |
| `/contracts score` | None | Shows the player their current Contract Score (CS) in chat, and their prestige tier when the track is on. |
[⭐ Premium](/editions/ "Premium feature (see Editions)") `/contracts hud` and `/contracts autopin` both act on the HUD, which is premium. On the lite edition the HUD does not exist, so the arguments are unrecognized and the command simply opens the menu.
## Admin commands
[Section titled “Admin commands”](#admin-commands)
Require the `contracts.admin` permission. Run them from the server console or the F1 console.
| Command | Arguments | Description |
| -------------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `contracts.rotate_category` | `` | Immediately re-rolls one category’s active contracts and reschedules its next rotation. |
| `contracts.add_to_rotation` | ` ` | Puts one specific contract live in a category’s active rotation without re-rolling the rest. Also adds the contract to the category’s contract list if it is missing. If the category is dormant (had no contracts), this starts a fresh rotation. |
| `contracts.remove_from_rotation` | ` [confirm]` | Removes one contract from the active rotation only (it stays a candidate for the next cycle). In-progress players lose their work on that contract, logged as expired. Players who already completed it still get their reward. If any player has work in progress, the command reports the impact and does nothing until you re-run it with `confirm`. |
| `contracts.reset_all_progress` | None | Wipes every player’s contract progress and all unclaimed rewards. **Cannot be undone!** |
| `contracts.view_score` | `` | Shows a player’s Contract Score (CS). |
| `contracts.give_score` | ` ` | Grants a player Contract Score (CS). `amount` must be a whole number of at least 1. Their prestige tier follows the new score. |
| `contracts.take_score` | ` ` | Removes Contract Score (CS) from a player. Their CS never drops below zero, and their prestige tier follows it down. |
| `contracts.give_item` | ` [amount] [skinId] [name]` | Gives a player an item, with an optional skin and custom name. `amount` defaults to 1 and `skinId` to 0 (no skin). Everything after the skin ID is the item name, no quotes needed. Use `0` as the skin to only set a name. If the player’s inventory is full, the rest drops at their feet. Skins sold by Facepunch only go on the item for players who own them. The player must be on the server (awake or asleep) and alive. |
| `contracts.wipe_scores` | None | Resets every player’s Contract Score (CS) to zero and clears their claimed prestige tiers. **Cannot be undone!** |
[⭐ Premium](/editions/ "Premium feature (see Editions)") `contracts.give_item` is premium. On the lite edition the command does not exist.
### Giving items from a shop or kit
[Section titled “Giving items from a shop or kit”](#giving-items-from-a-shop-or-kit)
`contracts.give_item` can be used outside of Contracts! Shop and kit plugins can run `contracts.give_item` when a player buys or redeems something. Put the plugin’s own Steam ID placeholder where the player goes:
* Shop or Kits by Mevent: `contracts.give_item %steamid% paper 100 3731371060 Cash`
* ServerRewards or GUIShop: `contracts.give_item $player.id paper 100 3731371060 Cash`
These shops have no amount placeholder, so the amount is part of the line. Make one shop entry per bundle size (10, 50, 100).
## Debug commands
[Section titled “Debug commands”](#debug-commands)
Also require `contracts.admin`. These are for troubleshooting and support, so you won’t need them in normal operation. Output prints to the console you run them from.
| Command | Arguments | Description |
| ------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `contracts.debug.hooks` | None | Lists every game hook Contracts uses and whether it is currently subscribed or not. Contracts automatically unsubscribes from hooks when no active contract’s objective needs it. |
| `contracts.debug.reset_data` | `` | Resets **all** plugin data (contracts, categories, presets, progress, preferences, pending rewards) to defaults, then reloads the plugin. You must pass the literal `yes` to confirm. **Cannot be undone!** |
| `contracts.debug.refresh.all` | None | Rebuilds every objective cache from the current data. |
| `contracts.debug.tainted_items` | None | Prints how many items are flagged as tainted in the loot objective cache. This usually stays under 1000. Clear if it exceeds 100k (never happens in practice). |
| `contracts.debug.clear_tainted_items` | None | Clears the tainted-item set from the loot objective cache. |
| `contracts.debug.transfers` | None | Prints how many in-flight item transfers are tracked in the loot objective cache. Transfers should typically be completed within a single tick. It should ideally be `0` all the time. |
| `contracts.debug.clear_transfers` | None | Clears tracked item transfers from the loot objective cache. You can usually safely run this, because it’s unlikely you’ll clear any active transfers. |
### Cache inspection
[Section titled “Cache inspection”](#cache-inspection)
Each objective type keeps a cache for fast objective progress lookups on high-frequency game events. These commands dump the contents of one cache (or all of them) to the console for diagnosis. You will likely never need to run these. They’re only documented here for completeness and for support purposes.
| Command | Arguments | Description |
| ---------------------------------- | --------- | ------------------------------------- |
| `contracts.debug.cache.all` | None | Dumps every objective cache. |
| `contracts.debug.cache.attackers` | None | Dumps the attackers cache. |
| `contracts.debug.cache.kill` | None | Dumps the kill objective cache. |
| `contracts.debug.cache.damage` | None | Dumps the damage objective cache. |
| `contracts.debug.cache.loot` | None | Dumps the loot objective cache. |
| `contracts.debug.cache.gather` | None | Dumps the gather objective cache. |
| `contracts.debug.cache.harvest` | None | Dumps the harvest objective cache. |
| `contracts.debug.cache.craft` | None | Dumps the craft objective cache. |
| `contracts.debug.cache.heal` | None | Dumps the heal objective cache. |
| `contracts.debug.cache.fishing` | None | Dumps the fishing objective cache. |
| `contracts.debug.cache.dig` | None | Dumps the dig objective cache. |
| `contracts.debug.cache.plant` | None | Dumps the plant objective cache. |
| `contracts.debug.cache.card_swipe` | None | Dumps the card-swipe objective cache. |
| `contracts.debug.cache.crate_hack` | None | Dumps the crate-hack objective cache. |
# Core concepts
> How contracts, categories, presets, objectives, conditions, and rewards fit together.
Everything players interact with in Contracts is built from a handful of concepts. This page defines each one and shows how they nest. The pages that follow document every type in detail.
You author all of this with the [web editor](https://editor.rustcontracts.com). The shapes below are what it reads and writes.
## Glossary
[Section titled “Glossary”](#glossary)
### Contract
[Section titled “Contract”](#contract)
A single quest. A contract carries one or more **objectives** to complete and one or more **rewards** paid out when it is done and claimed. Unclaimed rewards are automatically granted at the next category rotation.
### Objective
[Section titled “Objective”](#objective)
One goal inside a contract: kill an animal, craft an item, hack a crate. Each objective tracks progress, and what “one unit” means depends on its type (one kill, versus the amount of an item gathered). See [Objectives](/contracts/objectives/).
### Condition
[Section titled “Condition”](#condition)
An optional rule attached to an objective that restricts **when** progress counts, for example “only at night”, or “only with a specific weapon”. Conditions are a powerful way to add variety and nuance to objectives, and they can be layered in different combinations to create interesting scenarios of different difficulty. The same objective with no condition can be a lot harder when you add conditions.
### Reward
[Section titled “Reward”](#reward)
What a contract pays out on completion.
### Category
[Section titled “Category”](#category)
A group of contracts that **rotates** on its own timer that you define in seconds (e.g: hourly, daily, weekly, or for the whole wipe, when disabled). You define which contracts belong to which categories and the category rotation keeps track of which contracts are currently active for a rotation.
### Preset
[Section titled “Preset”](#preset)
A named, reusable list of Rust item or entity shortnames (or any string really). Many objective and condition fields accept a preset reference written as `@presetName` in place of a literal list. Define a list once (say, all the scientist types into one `@scientists` preset) and reference it from every contract that needs it. At runtime, the plugin swaps preset references for the actual lists of strings, so the contract authoring experience is easier and more efficient. You can even reference **presets inside other presets**! (Preset-ception!)
## How they nest
[Section titled “How they nest”](#how-they-nest)
```text
Category (rotates a pool of contracts on a timer)
└─ Contract
├─ Objective
| └─ Condition (optional)
└─ Reward
Preset (referenced as @name from objective/condition item & entity lists)
```
A category draws from a set of contracts. Each contract holds its objectives and rewards. Objectives may carry conditions. Presets sit to the side: they are shared lists that contracts point at rather than things a contract contains.
# Configuration
> Where the Contracts config file lives, how to reload it, and a map of every section.
The **config file** controls how the plugin behaves: the command that opens the menu, permissions, the HUD, logging, and more. Edit it by hand in any text editor, or open it in the [web editor](https://editor.rustcontracts.com). The editor has a config section next to your contracts, categories, and presets, and it validates every setting as you change it.
## Where it lives
[Section titled “Where it lives”](#where-it-lives)
* Oxide
`oxide/config/Contracts.json`
* Carbon
`carbon/config/Contracts.json`
After editing it, reload the plugin for changes to take effect:
* Oxide
```text
o.reload Contracts
```
* Carbon
```text
c.reload Contracts
```
Tip
If a value is invalid, the plugin does not crash: it logs a warning, falls back to the default, and keeps running. Watch the server console after a reload to catch typos.
## The shape
[Section titled “The shape”](#the-shape)
The file has one block per section. The full shape looks like this, with each block detailed on its own page below:
```json
{
"Command": [ ... ],
"Permissions": { ... },
"Rewards": { ... },
"Score": { ... },
"Prestige": { ... },
"Progress": { ... },
"Conditions": { ... },
"HUD": { ... },
"Reminders": { ... },
"Logging": { ... },
"Misc": { ... },
"UI": { ... },
"Version": { ... }
}
```
## Sections
[Section titled “Sections”](#sections)
[Command](/configuration/command/)The chat and console command(s) that open the Contracts menu.
[Permissions](/configuration/permissions/)The permissions that gate access to Contracts, and the per-permission contract limits.
[Rewards](/configuration/rewards/)How rewards show up in the menu, and the multipliers that decide what they pay each player.
[Score](/configuration/score/)Controls players' Contract Score (CS) behavior.
[Prestige](/configuration/prestige/)Turns the prestige track on or off and sets the order players claim tiers in.
[Progress](/configuration/progress/)Controls players' contract progress behavior.
[Conditions](/configuration/conditions/)How the plugin orders condition checks when evaluating objective progress.
[HUD](/configuration/hud/)Position and collapse behavior of the on-screen contract tracker.
[Reminders](/configuration/reminders/)Chat or toast nudges for contracts a player can accept and rewards they have not claimed.
[UI](/configuration/ui/)Re-theme the Contracts UI with your own palette colors and per-type icons.
[Logging](/configuration/logging/)Turn on event logging, choose a format, and customize each event's log line.
[Misc](/configuration/misc/)Miscellaneous settings, including the chat icon shown next to the plugin's messages.
## Version
[Section titled “Version”](#version)
The `Version` block is written by the plugin to track which config format it last wrote. Leave it alone: it is managed automatically and is not a setting you tune. If you upgrade Contracts and the format changes, the plugin migrates the file and updates this stamp for you.
# Command
> The chat and console command(s) that open the Contracts menu.
Sets which command(s) open the Contracts menu. Players run it in chat (e.g. `/contracts`), and it also works from the server console. Players can also open the menu from the [menu shortcut](/configuration/ui/#menu-shortcut) on their inventory screen.
```json
{
"Command": ["contracts"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Default | Description |
| --------- | --------- | --------------- | ---------------------------------------------------------------------------- |
| `Command` | string\[] | `["contracts"]` | Chat commands that open the contracts menu. Reminders mention the first one. |
## Notes
[Section titled “Notes”](#notes)
* List multiple names to offer aliases, for example `["contracts", "quests", "c"]`.
* Do not include the leading slash. Players add it themselves in chat.
* Empty entries are dropped, and an empty list falls back to `["contracts"]`.
# Conditions
> How the plugin orders condition checks when evaluating objective progress.
Controls the order in which the plugin evaluates an objective’s [conditions](/contracts/conditions/) on each qualifying action. This is a performance setting: it does not change which actions count, only the order checks run in. Cheaper checks run first so the plugin can short-circuit and skip the expensive ones.
```json
{
"Conditions": {
"Condition Ordering": "Optimized"
}
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Default | Description |
| -------------------- | ---------------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Condition Ordering` | enum: None \| Optimized \| RootOptimized | `"Optimized"` | How the plugin orders condition checks at load. Optimized sorts all conditions by cost so cheap checks run first, including inside And/Or groups. RootOptimized sorts only the top level and keeps nested order. None keeps the order from the data files. |
## Values
[Section titled “Values”](#values)
| Value | What it does |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `None` | Preserve the order conditions are written in. Use this if you ever need evaluation order to match the file exactly. |
| `Optimized` | Reorder by estimated cost, including conditions nested inside `And`/`Or`, so the cheapest checks run first. Best throughput, and the default. |
| `RootOptimized` | Reorder only the top-level conditions by cost, leaving conditions nested inside `And`/`Or` in their written order. |
Caution
Unless you have a specific reason to change it, you really should leave this on `Optimized`. Some conditions are much more expensive to check than others. For example, `TimeOfDay` is very cheap: it just checks the in-game time. `PlayerWear` is expensive: it has to check the player’s entire wear slots. This may not seem like much, but imagine a `Damage` objective with a `PlayerWear` condition that runs on every hit of a minigun.
# HUD
> Position and collapse behavior of the on-screen contract tracker.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Controls where the on-screen contract tracker (the HUD that shows a player’s pinned contracts) sits and which way it collapses.
```json
{
"HUD": {
"Anchor Min": "0.0 0.60",
"Anchor Max": "0.15 0.87",
"Collapse Direction": "TopLeft"
}
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Default | Description |
| -------------------- | ------------------------------------------------------ | ------------- | --------------------------------------------------------------------------------------------------------------- |
| `Anchor Min` | string | `"0.0 0.60"` | Bottom-left corner of the HUD as screen coordinates "x y", each from 0 to 1 starting at the bottom left. |
| `Anchor Max` | string | `"0.15 0.87"` | Top-right corner of the HUD as screen coordinates "x y", each from 0 to 1 starting at the bottom left. |
| `Collapse Direction` | enum: TopLeft \| TopRight \| BottomLeft \| BottomRight | `"TopLeft"` | Corner the HUD shrinks toward when a player collapses it. Pick the corner closest to where the HUD is anchored. |
## Anchors
[Section titled “Anchors”](#anchors)
> See [Oxide CUI Reference](https://docs.oxidemod.com/guides/developers/basic-cui/basic-cui#cuirecttransformcomponent)
Both anchors are a pair of screen fractions written as `"x y"`, where `0 0` is the **bottom-left** of the screen and `1 1` is the **top-right** (the same convention Rust UI uses). `Anchor Min` is the panel’s lower-left corner and `Anchor Max` is its upper-right corner.
So the defaults (`"0.0 0.60"` to `"0.15 0.87"`) place the HUD against the left edge, in the upper portion of the screen. To move it to the right edge, raise the x values, for example `"0.85 0.60"` to `"1.0 0.87"`.
An anchor that is not two numbers is rejected on load (a warning is printed) and falls back to the default.
## Collapse direction
[Section titled “Collapse direction”](#collapse-direction)
`Collapse Direction` sets which corner the panel keeps fixed as it expands and collapses, so the HUD grows away from the screen edge it sits against rather than off-screen. Match it to where you placed the panel: a left-edge HUD reads best with `TopLeft` or `BottomLeft`, a right-edge HUD with `TopRight` or `BottomRight`.
# Logging
> Turn on event logging, choose a format, and customize each event's log line.
Records contract events (accepted, completed, claimed, and more) to a log file. Logging is **off by default**: set a `Mode`, then enable the individual events you care about.
```json
{
"Logging": {
"Mode": "None",
"Retention Days": 30,
"Contract Accepted": { "Enabled": false, "Format (Text Mode)": "..." }
}
}
```
## Mode and retention
[Section titled “Mode and retention”](#mode-and-retention)
| Field | Type | Default | Description |
| ---------------- | --------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `Mode` | enum: None \| Text \| JSONL | `"None"` | Log file format. None turns logging off. Text writes one line per event using each event's format. JSONL writes one JSON object per line. |
| `Retention Days` | integer | `30` | Days of log files to keep. Older files get deleted. 0 or less keeps everything forever. |
Pick `JSONL` mode if you want the web editor’s stats graphs.
Each event below has its own `Enabled` flag and, for `Text` mode, a `Format (Text Mode)` string. The format is a template: the `{tokens}` in it are replaced with the event’s values when the line is written. In `JSONL` mode the tokens are emitted as fields instead, so the format string is not used.
Every event also accepts the five time tokens listed first in each table, in addition to the default format’s tokens.
## Contract Accepted
[Section titled “Contract Accepted”](#contract-accepted)
Logged when a player accepts a contract. Default `Enabled`: `false`.
```text
[{timestamp_utc}] {player_name} ({player_id}) accepted contract {contract_name} ({contract_id}) in {category_name} ({category_id})
```
| Token | Meaning |
| ------------------- | ---------------------------------------------------------------- |
| `{timestamp_utc}` | Event time in UTC (YYYY-MM-DD HH:MM:SS). |
| `{timestamp_iso}` | Event time in UTC, ISO 8601. |
| `{timestamp_local}` | Event time in the server's local timezone (YYYY-MM-DD HH:MM:SS). |
| `{time_utc}` | Time of day in UTC (HH:MM:SS). |
| `{time_local}` | Time of day in the server's local timezone (HH:MM:SS). |
| `{player_name}` | The player's display name. |
| `{player_id}` | The player's Steam ID. |
| `{contract_name}` | The contract's title. |
| `{contract_id}` | The contract's id (its key in the data file). |
| `{category_name}` | The category's name. |
| `{category_id}` | The category's id. |
## Contract Completed
[Section titled “Contract Completed”](#contract-completed)
Logged when a player finishes all of a contract’s objectives. Default `Enabled`: `false`.
```text
[{timestamp_utc}] {player_name} ({player_id}) completed contract {contract_name} ({contract_id}) in {category_name} ({category_id}) in {duration}s
```
| Token | Meaning |
| ------------------- | ---------------------------------------------------------------- |
| `{timestamp_utc}` | Event time in UTC (YYYY-MM-DD HH:MM:SS). |
| `{timestamp_iso}` | Event time in UTC, ISO 8601. |
| `{timestamp_local}` | Event time in the server's local timezone (YYYY-MM-DD HH:MM:SS). |
| `{time_utc}` | Time of day in UTC (HH:MM:SS). |
| `{time_local}` | Time of day in the server's local timezone (HH:MM:SS). |
| `{player_name}` | The player's display name. |
| `{player_id}` | The player's Steam ID. |
| `{contract_name}` | The contract's title. |
| `{contract_id}` | The contract's id (its key in the data file). |
| `{category_name}` | The category's name. |
| `{category_id}` | The category's id. |
| `{duration}` | Seconds taken to complete, from acceptance to finish. |
## Contract Claimed
[Section titled “Contract Claimed”](#contract-claimed)
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Logged when a player claims a completed contract’s reward. Default `Enabled`: `false`.
```text
[{timestamp_utc}] {player_name} ({player_id}) claimed contract {contract_name} ({contract_id}) in {category_name} ({category_id})
```
| Token | Meaning |
| ------------------- | ---------------------------------------------------------------- |
| `{timestamp_utc}` | Event time in UTC (YYYY-MM-DD HH:MM:SS). |
| `{timestamp_iso}` | Event time in UTC, ISO 8601. |
| `{timestamp_local}` | Event time in the server's local timezone (YYYY-MM-DD HH:MM:SS). |
| `{time_utc}` | Time of day in UTC (HH:MM:SS). |
| `{time_local}` | Time of day in the server's local timezone (HH:MM:SS). |
| `{player_name}` | The player's display name. |
| `{player_id}` | The player's Steam ID. |
| `{contract_name}` | The contract's title. |
| `{contract_id}` | The contract's id (its key in the data file). |
| `{category_name}` | The category's name. |
| `{category_id}` | The category's id. |
## Contract Abandoned
[Section titled “Contract Abandoned”](#contract-abandoned)
Logged when a player abandons a contract they had accepted. Default `Enabled`: `false`.
```text
[{timestamp_utc}] {player_name} ({player_id}) abandoned contract {contract_name} ({contract_id}) in {category_name} ({category_id})
```
| Token | Meaning |
| ------------------- | ---------------------------------------------------------------- |
| `{timestamp_utc}` | Event time in UTC (YYYY-MM-DD HH:MM:SS). |
| `{timestamp_iso}` | Event time in UTC, ISO 8601. |
| `{timestamp_local}` | Event time in the server's local timezone (YYYY-MM-DD HH:MM:SS). |
| `{time_utc}` | Time of day in UTC (HH:MM:SS). |
| `{time_local}` | Time of day in the server's local timezone (HH:MM:SS). |
| `{player_name}` | The player's display name. |
| `{player_id}` | The player's Steam ID. |
| `{contract_name}` | The contract's title. |
| `{contract_id}` | The contract's id (its key in the data file). |
| `{category_name}` | The category's name. |
| `{category_id}` | The category's id. |
## Contract Expired
[Section titled “Contract Expired”](#contract-expired)
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Logged when an accepted contract expires because its category rotated before the player finished it. Default `Enabled`: `false`.
```text
[{timestamp_utc}] {player_name} ({player_id}) expired contract {contract_name} ({contract_id}) in {category_name} ({category_id}) after {duration}s
```
| Token | Meaning |
| ------------------- | ---------------------------------------------------------------- |
| `{timestamp_utc}` | Event time in UTC (YYYY-MM-DD HH:MM:SS). |
| `{timestamp_iso}` | Event time in UTC, ISO 8601. |
| `{timestamp_local}` | Event time in the server's local timezone (YYYY-MM-DD HH:MM:SS). |
| `{time_utc}` | Time of day in UTC (HH:MM:SS). |
| `{time_local}` | Time of day in the server's local timezone (HH:MM:SS). |
| `{player_name}` | The player's display name. |
| `{player_id}` | The player's Steam ID. |
| `{contract_name}` | The contract's title. |
| `{contract_id}` | The contract's id (its key in the data file). |
| `{category_name}` | The category's name. |
| `{category_id}` | The category's id. |
| `{duration}` | Seconds the contract was active before expiring. |
## Reward Given
[Section titled “Reward Given”](#reward-given)
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Logged when a reward is handed to a player. Default `Enabled`: `false`.
`amount` is what the player actually received. When a [reward multiplier tier](/configuration/rewards/#multipliers) raised or lowered it, `multiplier` carries the factor that was used. JSONL lines leave `multiplier` out when no tier changed the amount.
Items dropped at the player’s feet because the inventory was full are logged too.
```text
[{timestamp_utc}] {player_name} ({player_id}) received {reward_type} reward: {reward_detail} from contract {contract_name} ({contract_id})
```
| Token | Meaning |
| ------------------- | ------------------------------------------------------------------------------------------------------------ |
| `{timestamp_utc}` | Event time in UTC (YYYY-MM-DD HH:MM:SS). |
| `{timestamp_iso}` | Event time in UTC, ISO 8601. |
| `{timestamp_local}` | Event time in the server's local timezone (YYYY-MM-DD HH:MM:SS). |
| `{time_utc}` | Time of day in UTC (HH:MM:SS). |
| `{time_local}` | Time of day in the server's local timezone (HH:MM:SS). |
| `{player_name}` | The player's display name. |
| `{player_id}` | The player's Steam ID. |
| `{reward_type}` | The reward type: Item, Economics, Command, or ServerRewards. |
| `{reward_detail}` | A human-readable description of the reward (e.g. item and quantity, or amount). |
| `{reward_name}` | The reward's display title. |
| `{reward_id}` | The reward's id. |
| `{objective_id}` | The objective's id, when the reward came from an objective. Empty for a contract reward. |
| `{amount}` | The amount handed to the player, after any reward multiplier. |
| `{multiplier}` | The reward multiplier applied to the amount. 1 when no tier matched and JSON lines leave the field out then. |
| `{contract_name}` | The contract's title. |
| `{contract_id}` | The contract's id (its key in the data file). |
| `{category_name}` | The category's name. |
| `{category_id}` | The category's id. |
## Rotation Started
[Section titled “Rotation Started”](#rotation-started)
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Logged when a category rotates to a new set of contracts. Default `Enabled`: `false`.
```text
[{timestamp_utc}] Category {category_name} ({category_id}) rotated. Contracts: {contract_ids}.
```
| Token | Meaning |
| ------------------- | ---------------------------------------------------------------- |
| `{timestamp_utc}` | Event time in UTC (YYYY-MM-DD HH:MM:SS). |
| `{timestamp_iso}` | Event time in UTC, ISO 8601. |
| `{timestamp_local}` | Event time in the server's local timezone (YYYY-MM-DD HH:MM:SS). |
| `{time_utc}` | Time of day in UTC (HH:MM:SS). |
| `{time_local}` | Time of day in the server's local timezone (HH:MM:SS). |
| `{category_name}` | The category's name. |
| `{category_id}` | The category's id. |
| `{contract_ids}` | The ids of the contracts now in rotation, comma-separated. |
## Objective Completed
[Section titled “Objective Completed”](#objective-completed)
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Logged when a player finishes a single objective within a contract. Default `Enabled`: `false`.
```text
[{timestamp_utc}] {player_name} ({player_id}) completed objective {objective_name} (#{objective_id}) in contract {contract_name} ({contract_id})
```
| Token | Meaning |
| ------------------- | ---------------------------------------------------------------- |
| `{timestamp_utc}` | Event time in UTC (YYYY-MM-DD HH:MM:SS). |
| `{timestamp_iso}` | Event time in UTC, ISO 8601. |
| `{timestamp_local}` | Event time in the server's local timezone (YYYY-MM-DD HH:MM:SS). |
| `{time_utc}` | Time of day in UTC (HH:MM:SS). |
| `{time_local}` | Time of day in the server's local timezone (HH:MM:SS). |
| `{player_name}` | The player's display name. |
| `{player_id}` | The player's Steam ID. |
| `{objective_name}` | The objective's title. |
| `{objective_id}` | The objective's id (its key in the contract). |
| `{objective_type}` | The objective's type (e.g. Kill, Gather, Loot). |
| `{contract_name}` | The contract's title. |
| `{contract_id}` | The contract's id (its key in the data file). |
| `{category_name}` | The category's name. |
| `{category_id}` | The category's id. |
## Prestige Tier Reached
[Section titled “Prestige Tier Reached”](#prestige-tier-reached)
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Logged when a player’s Contract Score reaches a [prestige tier](/prestige/), one entry per tier crossed. Default `Enabled`: `false`.
```text
[{timestamp_utc}] {player_name} ({player_id}) reached prestige tier {tier} ({threshold} CS) with {score} CS
```
| Token | Meaning |
| ------------------- | ---------------------------------------------------------------------------- |
| `{timestamp_utc}` | Event time in UTC (YYYY-MM-DD HH:MM:SS). |
| `{timestamp_iso}` | Event time in UTC, ISO 8601. |
| `{timestamp_local}` | Event time in the server's local timezone (YYYY-MM-DD HH:MM:SS). |
| `{time_utc}` | Time of day in UTC (HH:MM:SS). |
| `{time_local}` | Time of day in the server's local timezone (HH:MM:SS). |
| `{player_name}` | The player's display name. |
| `{player_id}` | The player's Steam ID. |
| `{tier}` | The prestige tier's number, counting from 1. |
| `{threshold}` | The Contract Score the tier needs. |
| `{title}` | The tier's title, as written in the data file. Empty when the tier has none. |
| `{score}` | The player's Contract Score right after they reached the tier. |
## Prestige Tier Claimed
[Section titled “Prestige Tier Claimed”](#prestige-tier-claimed)
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Logged when a player claims a [prestige tier’s](/prestige/) rewards. Default `Enabled`: `false`.
```text
[{timestamp_utc}] {player_name} ({player_id}) claimed prestige tier {tier} ({threshold} CS)
```
| Token | Meaning |
| ------------------- | ---------------------------------------------------------------------------- |
| `{timestamp_utc}` | Event time in UTC (YYYY-MM-DD HH:MM:SS). |
| `{timestamp_iso}` | Event time in UTC, ISO 8601. |
| `{timestamp_local}` | Event time in the server's local timezone (YYYY-MM-DD HH:MM:SS). |
| `{time_utc}` | Time of day in UTC (HH:MM:SS). |
| `{time_local}` | Time of day in the server's local timezone (HH:MM:SS). |
| `{player_name}` | The player's display name. |
| `{player_id}` | The player's Steam ID. |
| `{tier}` | The prestige tier's number, counting from 1. |
| `{threshold}` | The Contract Score the tier needs. |
| `{title}` | The tier's title, as written in the data file. Empty when the tier has none. |
# Misc
> Miscellaneous settings, including the chat icon shown next to the plugin's messages.
Miscellaneous settings that don’t belong to any other section.
```json
{
"Misc": {
"Chat SteamID Icon": 76561198677754451,
"Hide Cooldown UI when window is below seconds": 10,
"Hide Burst UI when window is below seconds": 10,
"Use PlayerDLCAPI": true,
"Ignore HUD Pin Limit": false
}
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Default | Description |
| -------------------------------------------------------------------------------------------------------- | ------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Chat SteamID Icon` | string | `"76561198677754451"` | SteamID64 of the account whose avatar shows as the icon on the plugin's chat messages. |
| `Hide Cooldown UI when window is below seconds` [⭐ Premium](/editions/ "Premium feature (see Editions)") | integer | `10` | Hide the cooldown indicator on objectives whose cooldown window is shorter than this many seconds. Keeps very short cooldowns from flashing the UI. |
| `Hide Burst UI when window is below seconds` [⭐ Premium](/editions/ "Premium feature (see Editions)") | integer | `10` | Hide the burst indicator on objectives whose burst window is shorter than this many seconds. Keeps very short windows from flashing the UI. |
| `Use PlayerDLCAPI` [⭐ Premium](/editions/ "Premium feature (see Editions)") | boolean | `true` | Use the PlayerDLCAPI plugin, when installed, to check skin and DLC ownership before giving skinned item rewards. |
| `Ignore HUD Pin Limit` [⭐ Premium](/editions/ "Premium feature (see Editions)") | boolean | `false` | Let Max Tracked Contracts (HUD) go above 25, and give -1 its unlimited meaning back. The HUD redraws every pinned contract on each update, so high pin counts cost server performance. Leave this off unless you have measured it. |
## Notes
[Section titled “Notes”](#notes)
* `Chat SteamID Icon`: the plugin writes it as a plain number, and a quoted string works too (the web editor exports it quoted). Set it to `0` to disable the icon and use the default.
* `Ignore HUD Pin Limit`: the plugin caps [`Max Tracked Contracts (HUD)`](/configuration/permissions/) at 25 pins per player, and treats `-1` as 25 too. Turn this on to lift the cap and get `-1` (unlimited) back. Every HUD update redraws each pinned contract, so 100 pins send roughly 20 times the data 5 pins do, per player, on every progress tick. Turn it on only if you have measured your server and accept that cost.
* The two hide settings apply to [cooldown](/contracts/objectives/cooldown/) and [burst](/contracts/objectives/burst/) windows on objectives. Set `Hide Cooldown UI when window is below seconds` very high to always hide the cooldown UI, and `Hide Burst UI when window is below seconds` to `0` to always show the burst UI.
# Permissions
> The permissions that gate access to Contracts, and the per-permission contract limits.
Controls who can use Contracts, who counts as an admin, and how many contracts each group of players may run and pin to their HUD.
```json
{
"Permissions": {
"Use Contracts Features": "contracts.use",
"Admin": "contracts.admin",
"Max Active Contracts": {
"contracts.use": 3,
"contracts.vip": 5,
"contracts.admin": -1
},
"Max Paused Contracts": {
"contracts.use": 0,
"contracts.vip": 0,
"contracts.admin": -1
},
"Max Tracked Contracts (HUD)": {
"contracts.use": 2,
"contracts.vip": 5,
"contracts.admin": -1
}
}
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Default | Description |
| -------------------------------------------------------------------------------------- | ------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Use Contracts Features` | string | `"contracts.use"` | Permission a player needs to use contracts at all. Registered by the plugin on load. |
| `Admin` | string | `"contracts.admin"` | Permission for admin commands. Registered by the plugin on load. |
| `Max Active Contracts` | map | `{"contracts.use":3,"contracts.vip":5,"contracts.admin":-1}` | How many contracts a player can have active at once, per permission. A player with several of these permissions gets the highest cap. -1 means unlimited. Keys can be any permission name, including ones from other plugins. |
| `Max Paused Contracts` [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | `{"contracts.use":0,"contracts.vip":0,"contracts.admin":-1}` | How many contracts a player can keep paused at once, per permission. A player with several of these permissions gets the highest cap. -1 means unlimited. A player who holds none of them cannot pause, which is why every row ships at 0. Pausing is premium-only. |
| `Max Tracked Contracts (HUD)` [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | `{"contracts.use":2,"contracts.vip":5,"contracts.admin":-1}` | How many contracts a player can track on the HUD at once, per permission. A player with several of these permissions gets the highest cap. The plugin caps this at 25, and -1 (unlimited) also lands on 25, unless you turn on "Ignore HUD Pin Limit" in Misc. |
## How the limits resolve
[Section titled “How the limits resolve”](#how-the-limits-resolve)
`Max Active Contracts`, `Max Paused Contracts` and `Max Tracked Contracts (HUD)` are **maps from a permission to a number**. You grant the permissions to players (or groups) with your framework’s permission system, then set a limit per permission here.
When a player holds more than one of the listed permissions, the limit resolves like this:
* The player gets the **highest** limit among the permissions they hold. A VIP who also has `contracts.use` gets `5`, not `3`.
* If **any** held permission is set to `-1`, the player is **unlimited**, regardless of the other numbers.
* A value below `-1` is treated as `-1` (unlimited).
* `Max Tracked Contracts (HUD)` is the exception to `-1`. It stops at 25 pins per player, and `-1` resolves to 25 as well. See [the HUD pin limit](#the-hud-pin-limit) below.
* A player holding none of the listed permissions has no limit applied. For that reason, it’s important to include the `Use Contracts Features` permission in the map.
* `Max Paused Contracts`: A player holding none of its listed permissions gets `0`, so they cannot pause anything.
## The HUD pin limit
[Section titled “The HUD pin limit”](#the-hud-pin-limit)
Every HUD update does work for each contract a player has pinned, and objective progress can tick many times a second. Think of a Damage objective with a minigun, that’s exactly how I make performance decisions like this when developing Contracts. So `Max Tracked Contracts (HUD)` stops at **25 pins per player**, whatever the config says. A value above 25 resolves to 25 and the plugin prints a warning on load. `-1` resolves to 25 too, quietly, since it is the shipped default for `contracts.admin`.
The HUD scrolls, so nothing is hidden by the limit, it only bounds how much the plugin redraws.
To lift it, set `"Ignore HUD Pin Limit": true` in the [Misc section](/configuration/misc/). Then `Max Tracked Contracts (HUD)` accepts any value again and `-1` means unlimited. Read the note on that page first, because there’s a real performance cost.
## Notes
[Section titled “Notes”](#notes)
* Contracts registers permissions listed in the config file at startup.
# Prestige
> Turns the prestige track on or off and sets the order players claim tiers in.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Turns the [prestige track](/prestige/) on or off and sets the order players claim tiers in.
```json
{
"Prestige": {
"Enabled": true,
"Claim Tiers In Order": false
}
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Default | Description |
| ---------------------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Enabled` | boolean | `true` | Turns the prestige track on. Off hides the prestige view and the leaderboard tier column, skips the tier messages, and ignores every tier lock. The prestige data file still loads. |
| `Claim Tiers In Order` | boolean | `false` | Players have to claim their lowest claimable tier first. Higher tiers show a greyed out Claim button until the ones below are taken. Turn it on when tier rewards run commands that depend on order, like a chat tag group per tier. |
## Notes
[Section titled “Notes”](#notes)
* With `Enabled` off, the prestige button and view are hidden, the leaderboard drops its Tier column, the tier chat messages are skipped, and every `minPrestigeTier` lock on a contract or category is ignored. The plugin prints one console warning at load listing the contracts and categories it let through.
* `Claim Tiers In Order` makes players claim their lowest claimable tier first.
* The view’s page size lives under `UI` > `Prestige` instead (see [UI](/configuration/ui/#prestige)).
* Everything the track does, what players see, and how tier locks behave is on the [Prestige](/prestige/) page.
# Progress
> Controls players' contract progress behavior.
Controls players’ contract progress behavior.
```json
{
"Progress": {
"ResetOnWipe": true
}
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Default | Description |
| ------------- | ------- | ------- | ------------------------------------------------------------- |
| `ResetOnWipe` | boolean | `true` | Reset every player's contract progress when the server wipes. |
Set `ResetOnWipe` to `false` to keep progress across wipes.
## Notes
[Section titled “Notes”](#notes)
* This only controls the automatic reset on a new map. Admins can wipe all progress at any time with `contracts.reset_all_progress` (see [Commands](/commands/)).
# Reminders
> Chat or toast nudges for contracts a player can accept and rewards they have not claimed.
Reminders nudge players about things they can act on right now. There are two:
* **Available Contracts**: the player has a free contract slot and at least one contract they could accept right now.
* **Unclaimed Rewards**: the player holds an unclaimed contract reward, an unclaimed objective reward, or a [prestige tier](/prestige/) with rewards to claim.
A reminder only sends when its condition is true at that moment, and only to players with permission to use the plugin.
```json
{
"Reminders": {
"Available Contracts": {
"Enabled": true,
"Trigger": "Awake",
"Interval": null,
"Amount": 5,
"Cooldown": 300,
"Method": "Chat"
},
"Unclaimed Rewards": {
"Enabled": true,
"Trigger": "Awake",
"Interval": null,
"Amount": 5,
"Cooldown": 300,
"Method": "Chat"
}
}
}
```
## Fields
[Section titled “Fields”](#fields)
Both reminders take the same fields:
| Field | Type | Default | Description |
| ---------- | -------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Enabled` | boolean | `true` | Send this reminder. |
| `Trigger` | enum: Time \| Awake \| MenuClose | `"Awake"` | When the reminder fires. Time fires every Interval seconds. Awake fires when a player wakes up. MenuClose fires when a player closes the contracts menu. |
| `Interval` | integer \| null | `null` | Seconds between reminders when Trigger is Time. Must be above 0 for the Time trigger, the plugin resets it to 3600 at load otherwise. Other triggers ignore it, leave it null then. |
| `Amount` | integer (min -1) | `5` | How many times the reminder can fire before it goes quiet. The count resets when the player acts on it, like opening the menu or claiming a reward. -1 means no limit. |
| `Cooldown` | integer (min 0) | `300` | Seconds before the same reminder can fire again for a player. Only applies to the Awake and MenuClose triggers, Time paces itself with Interval. |
| `Method` | enum: Chat \| Toast | `"Chat"` | How the reminder shows up: a chat message or a toast popup in the bottom left. |
## Triggers
[Section titled “Triggers”](#triggers)
* `Time` sends the reminder every `Interval` seconds to connected, awake players. If you pick `Time` without a positive `Interval`, the plugin warns on load and uses `3600` (one hour).
* `Awake` sends it when the player wakes from sleep: after connecting, after death, after teleporting.
* `MenuClose` sends it when the player closes the Contracts menu.
Whatever the trigger, the reminder only sends if its condition is true at that moment, its cooldown has passed, and its `Amount` is not used up. Triggers on cooldown don’t count toward `Amount`.
## When the plugin stops reminding
[Section titled “When the plugin stops reminding”](#when-the-plugin-stops-reminding)
`Amount` caps how much a player gets nagged. Every sent reminder of a type counts toward it, and when the cap is reached that reminder goes quiet for that player. Acting on the reminder resets the count to zero:
* Accepting a contract resets Available Contracts reminders.
* Claiming a reward resets Unclaimed Rewards reminders, and claiming a prestige tier counts as claiming a reward.
Acting also starts the cooldown, so a player who just accepted or claimed gets quiet time instead of an instant follow-up.
Counts and cooldowns live in memory only. A player who disconnects starts fresh, and a server restart or plugin reload resets everyone.
## Players can turn reminders off
[Section titled “Players can turn reminders off”](#players-can-turn-reminders-off)
A player can silence all reminders for themselves with `/contracts reminders` or with the toggle in the preferences menu. That is their preference and it applies to them only. If reminders are turned off (`Enabled` is `false`) in the config, the plugin never sends them to anyone, regardless of their personal preference. So it’s recommended to leave reminders on and let players turn them off if they want.
# Rewards
> How rewards show up in the menu, and the multipliers that decide what they pay each player.
Two things configure rewards here:
* how rewards are presented in the menu
* the reward multipliers that pay higher-ranked players more for the same contract. (What each reward actually grants is defined per contract, not here. See the [reward reference](/contracts/rewards/).)
```json
{
"Rewards": {
"Show Ineligible Rewards": true,
"Multipliers": {
"Level Source": {
"Plugin": null,
"Method": null,
"Key": null
},
"Tiers": []
}
}
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Default | Description |
| ------------------------- | ------- | ------- | --------------------------------------------------------------------------------------------------------------- |
| `Show Ineligible Rewards` | boolean | `true` | Show rewards a player is not eligible for (missing the reward's permission) in the menu instead of hiding them. |
A reward is ineligible for a player when they lack the reward’s `eligiblePermissions` (see the [reward reference](/contracts/rewards/)). Left on, the menu still lists those rewards and marks them as ineligible for that player. Set it to `false` to hide them from that player instead. A reward set to [`hidden`](/contracts/rewards/#hidden-rewards) never shows, whatever this is set to.
## Multipliers
[Section titled “Multipliers”](#multipliers)
[⭐ Premium](/editions/ "Premium feature (see Editions)")
A multiplier tier defines who it matches and what those players get paid, per reward type. Contracts checks every tier against the player it is about to pay and each reward type takes the **highest multiplier** among the tiers that matched.
* A tier matches on a minimum level, on a permission, or on both. Both set means the player needs both.
* A tier with neither matches every player, which can be useful if you want to run double payout events.
* Tiers never stack, so a VIP tier can never cut a level tier’s payout. The order you list them in does not matter.
* Only amounts are multiplied: Economics, ServerRewards, Score and custom reward amounts, including the item count of an [Item definition](/custom-rewards/#item-definitions). Item reward quantities and Command rewards are not, and contract costs are never touched.
* ServerRewards, Score and Item definition counts are whole numbers, so the result is rounded. An item count never drops below 1. Economics and Command definition amounts keep their decimals.
* The menu shows the amount the player will actually get.
* On the Lite edition every multiplier is 1, regardless of what is configured.
| Field | Type | Default | Description |
| ------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Tiers` | object\[] | `[]` | Multiplier tiers, each with the match condition and what it pays them. When several tiers match a player, every reward variant takes the highest multiplier across them. |
### Level Source
[Section titled “Level Source”](#level-source)
Instead of implementing different leveling systems explicitly (Skill Tree, XPerience, ZLevels, etc.), Contracts takes a dependency-agnostic approach where you specify: the plugin, the method on it that gives back a level and a key if that plugin tracks more than one level. Contracts calls that method with the player every time it pays a reward, so a level up counts right away. While it makes it a bit less straightforward than hardcoding support for each plugin, this means the reward multipliers work with multiple current plugins out-of-the-box immediately and future potential plugins as well.
#### Method Signature
[Section titled “Method Signature”](#method-signature)
This approach relies on the plugin using a common method signature that every other plugin used to design this feature followed.
```csharp
MethodName(player: BasePlayer | ulong | string, key?: string)
```
If it doesn’t, Contracts won’t be able to read the player’s level correctly. In that case, you can:
1. Contact the plugin developer to add a compatible method for reading the player’s level.
2. Use a permission-based approach instead, which does not rely on the plugin’s level method and is actually simpler to use.
3. Request an update to the plugin to include a compatible method signature (in the #feedback Discord channel).
| Field | Type | Default | Description |
| -------- | -------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Plugin` | string \| null | `null` | Name of the plugin Contracts asks for a player's level, spelled the way the plugin list spells it ("SkillTree", "XPerience"). null means no level source, so tiers with a Min Level never match and tiers with a Permission keep working. |
| `Method` | string \| null | `null` | The method on that plugin that gives back a level, taken from its API docs (e.g. "GetPlayerLevel" for Skill Tree). Contracts calls it with the player and reads the number it returns. Set it together with Plugin. |
| `Key` | string \| null | `null` | Second argument for plugins that track several levels, usually a skill name ("WC" for ZLevels woodcutting, "Mining" for Skill System). Leave it null for plugins with one overall level. |
Here’s a couple configuration examples from popular leveling plugins at the time of writing:
| Leveling plugin | `Plugin` | `Method` | `Key` | Notes |
| ------------------ | ------------------- | ---------------- | ------------------------------------------------------- | --------------------------------------------------------- |
| Skill Tree | `SkillTree` | `GetPlayerLevel` | leave `null` | One overall level. A player it has never seen is level 0. |
| XPerience | `XPerience` | `GetXPCache` | `"level"` | One overall level. |
| ZLevels Remastered | `ZLevelsRemastered` | `GetLevel` | a skill code: `"WC"`, `"M"`, `"S"`, `"A"`, `"C"`, `"F"` | One level per skill, so pick the skill you pay on. |
| Skill System | `SkillSystem` | `GetSkillLevel` | a skill name: `"Mining"`, `"Woodcutting"` | One level per skill, so pick the skill you pay on. |
ZLevels Remastered and Skill System track a level per skill instead of one overall level. Pick the one skill you want to pay on with `Key`, or use that plugin’s own level rewards to grant a permission and match a tier on that permission instead.
Any plugin not listed above with some kind of “give me this player’s level” method can still work. If the plugin is not loaded or the method name is wrong, Contracts prints **one** warning in the console (so it doesn’t spam repeatedly) and tiers with a `Min Level` stop matching. Tiers that match on a permission keep paying.
You might not need a level source!
A tier does not need a level source at all. Leave `Min Level` at `null`, put a permission in and the tier pays anyone who holds it. Skill Tree hands out its own prestige permissions, so `skilltree.prestige1` in a tier works just as well (and in my opinion, is often simpler). A VIP group works the same way with whatever permission you already grant it. These are multiplier tiers and Skill Tree’s own reset loop, unrelated to the Contracts [prestige track](/prestige/).
### Tiers
[Section titled “Tiers”](#tiers)
| Field | Type | Default | Description |
| ----------------- | ---------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Min Level` | number \| null (min 0) | `null` | Lowest level a player needs for this tier to match. Needs a Level Source to work. null means the tier does not look at the level. |
| `Permission` | string \| null | `null` | Permission a player needs for this tier to match. Any permission name works, including one another plugin grants. null means the tier does not look at permissions. A tier with no Min Level and no Permission matches every player. |
| `Economics` | number | `1` | Multiplies Economics reward amounts. 1 pays the authored amount. |
| `ServerRewards` | number | `1` | Multiplies ServerRewards (RP) reward amounts, rounded to a whole number. 1 pays the authored amount. |
| `Score` | number | `1` | Multiplies contract score reward amounts, rounded to a whole number. 1 pays the authored amount. |
| `SkillTreeXp` | number | `1` | Multiplies Skill Tree XP reward amounts. 1 pays the authored amount. |
| `SkillTreePoints` | number | `1` | Multiplies Skill Tree skill point reward amounts, rounded to a whole number. Point amounts are small, so 1.5x on 1 point pays 2. 1 pays the authored amount. |
| `Custom` | map | `{}` | Multipliers for custom reward amounts, keyed by the custom reward key. The key is matched without case, like every other custom reward lookup. A key you omit pays the authored amount, and a custom reward with no amount is never multiplied. |
### A full example
[Section titled “A full example”](#a-full-example)
Skill Tree levels paying more the higher a player climbs, plus a tier for players who hold Skill Tree’s own prestige permission:
```json
"Multipliers": {
"Level Source": {
"Plugin": "SkillTree",
"Method": "GetPlayerLevel",
"Key": null
},
"Tiers": [
{ "Min Level": 10, "Permission": null, "Economics": 1.0, "ServerRewards": 1.0, "Score": 1.0, "SkillTreeXp": 1.25, "SkillTreePoints": 1.0, "Custom": { "EasyHeliSignal": 1.0 } },
{ "Min Level": 20, "Permission": null, "Economics": 1.25, "ServerRewards": 1.0, "Score": 1.0, "SkillTreeXp": 1.5, "SkillTreePoints": 1.0, "Custom": { "EasyHeliSignal": 1.5 } },
{ "Min Level": 90, "Permission": null, "Economics": 2.5, "ServerRewards": 1.0, "Score": 1.0, "SkillTreeXp": 5.0, "SkillTreePoints": 1.0, "Custom": { "EasyHeliSignal": 2.0 } },
{ "Min Level": null, "Permission": "skilltree.prestige1", "Economics": 1.5, "ServerRewards": 1.0, "Score": 1.0, "SkillTreeXp": 1.0, "SkillTreePoints": 1.0, "Custom": {} }
]
}
```
A player at level 95 matches the first three tiers, so they get 2.5x Economics, 5x Skill Tree XP and 2x on the `EasyHeliSignal` custom reward, the highest of each. Give that player the `skilltree.prestige1` permission and their Economics stays at 2.5x, because 2.5 beats that last tier’s 1.5.
`EasyHeliSignal` is only an example key. A `Custom` entry does nothing unless you have a [custom reward](/custom-rewards/) under that exact key, so put your own keys there.
# Score
> Controls players' Contract Score (CS) behavior.
Controls players’ Contract Score (CS) behavior.
```json
{
"Score": {
"ResetOnWipe": true
}
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Default | Description |
| ------------- | ------- | ------- | ------------------------------------------------- |
| `ResetOnWipe` | boolean | `true` | Reset every player's score when the server wipes. |
Set `ResetOnWipe` to `false` to keep CS across wipes.
## Notes
[Section titled “Notes”](#notes)
* This only controls the automatic reset on a new map. Admins can reset CS at any time with `contracts.wipe_scores` (see [Commands](/commands/)).
* Contract Score is granted by the [Score reward](/contracts/rewards/score/).
* `ResetOnWipe` also governs the [prestige track](/prestige/#wipes), and `contracts.wipe_scores` clears players’ claimed tiers along with their CS.
# UI
> Re-theme the Contracts UI with your own palette colors and per-type icons.
Lets you re-theme the Contracts UI. Every named palette color can be replaced, and every icon the plugin ships can get its own image and color, from objective types down to the countdown clock. All of it is optional: a value of `null` means “use the built-in look”, and a fresh config ships with everything set to `null`. The section also holds behavior settings for the contracts menu under `Menu`.
```json
{
"UI": {
"Colors": {
"Accent Primary": "#4dc1ff",
"Accent Muted": "#285278",
"Accent Background": "#001221",
"Accent Border": "#284b5a",
"Text": null,
"Danger": null
},
"Icons": {
"Objectives": {
"Kill": { "Icon": "https://example.com/skull.png", "Color": "#ff4d4d", "Raw": false }
},
"Conditions": {
"Weapon": { "Icon": null, "Color": "rgb(255, 200, 120)", "Raw": false }
},
"Rewards": {
"Command": { "Icon": "https://example.com/vip.png", "Color": null, "Raw": true }
},
"Chips": {
"Preset": { "Icon": "https://example.com/crate.png", "Color": null, "Raw": true },
"Npc": { "Icon": null, "Color": "#ff4d4d", "Raw": false }
},
"Ui": {
"Clock": { "Icon": null, "Color": "#4dc1ff", "Raw": false },
"ItemFallback": { "Icon": "https://example.com/crate.png", "Color": null, "Raw": true }
},
"Monuments": {
"launch_site_1": { "Icon": "https://example.com/rocket.png", "Color": null, "Raw": true }
}
},
"Menu": {
"Contracts Per Page": 10
},
"Leaderboard": {
"Enabled": true,
"Players Per Page": 10
},
"Prestige": {
"Tiers Per Page": 25
}
}
}
```
The example above is trimmed for readability. The real file lists every color token and every type key, so you never have to guess a name: set the ones you want and leave the rest `null`.
## Colors
[Section titled “Colors”](#colors)
Every entry accepts the same color formats used elsewhere in the UI: `#rrggbb`, `#rrggbbaa`, `rgb(r, g, b)`, `rgba(r, g, b, a)`, `hsl(h, s%, l%)` and `hsla(h, s%, l%, a)`. A color that fails to parse prints a warning at load and falls back to white, the plugin never refuses to start over a color.
Overriding a color carries through everywhere it appears, including the translucent variants the UI derives from it (hover states, progress fills, dimmed text and so on).
### Surfaces and text
[Section titled “Surfaces and text”](#surfaces-and-text)
The dark base coat of the UI: window backgrounds, panels and text.
| Setting | Default |
| ------------------ | --------------------------- |
| `Surface Backdrop` | `rgba(0, 0, 0, 0.95)` |
| `Surface Base` | `rgb(10, 10, 10)` |
| `Surface Raised` | `rgb(18, 18, 18)` |
| `Surface Elevated` | `rgb(28, 28, 28)` |
| `Surface Active` | `rgba(40, 40, 40, 1)` |
| `Border` | `rgba(255, 255, 255, 0.1)` |
| `Text` | `rgb(220, 220, 220)` |
| `Text Contrast` | `rgb(10, 10, 10)` |
| `Text Muted` | `rgba(229, 229, 229, 0.2)` |
| `Text Disabled` | `rgba(150, 150, 150, 0.35)` |
| `Transparent` | `rgba(0, 0, 0, 0)` |
### Accent
[Section titled “Accent”](#accent)
The gold highlight family: primary buttons, the selected category, progress accents. Swapping these four is the quickest way to make the UI match your server’s branding.
| Setting | Default |
| ------------------- | --------- |
| `Accent Primary` | `#ffc14d` |
| `Accent Muted` | `#785f28` |
| `Accent Background` | `#211200` |
| `Accent Border` | `#5a4b28` |
### Semantic colors
[Section titled “Semantic colors”](#semantic-colors)
Status colors for confirmations, warnings and errors.
| Setting | Default |
| --------- | --------- |
| `Danger` | `#cc272e` |
| `Success` | `#4aa651` |
| `Info` | `#238ea9` |
| `Warning` | `#d09945` |
### Color families
[Section titled “Color families”](#color-families)
General-purpose colors used across icons, chips and highlights, in a bright, base and dark shade each.
| Setting | Default | Setting | Default | Setting | Default |
| --------------- | --------- | -------- | --------- | ------------- | --------- |
| `Bright Red` | `#ea7467` | `Red` | `#db3830` | `Dark Red` | `#540502` |
| `Bright Green` | `#60d768` | `Green` | `#22bc1f` | `Dark Green` | `#024501` |
| `Bright Blue` | `#67c5ea` | `Blue` | `#3082db` | `Dark Blue` | `#011f41` |
| `Bright Yellow` | `#eaea67` | `Yellow` | `#c9c928` | `Dark Yellow` | `#393901` |
| `Bright Orange` | `#e5a854` | `Orange` | `#d2961d` | `Dark Orange` | `#3c2302` |
| `Bright Purple` | `#ab60d7` | `Purple` | `#a825c6` | `Dark Purple` | `#48044a` |
### Foreground aliases
[Section titled “Foreground aliases”](#foreground-aliases)
Text colors used on top of the matching semantic or accent background. By default each one follows another token (`Danger Foreground` and `Info Foreground` follow `Text`, the other three follow `Text Contrast`), and they keep following it: override `Text` and `Danger Foreground` moves with it, unless you override `Danger Foreground` itself.
| Setting | Default |
| -------------------- | --------------- |
| `Danger Foreground` | `Text` |
| `Success Foreground` | `Text Contrast` |
| `Info Foreground` | `Text` |
| `Warning Foreground` | `Text Contrast` |
| `Accent Foreground` | `Text Contrast` |
## Icons
[Section titled “Icons”](#icons)
Six categories of icons: `Objectives`, `Conditions`, `Rewards`, `Chips`, `Ui` and `Monuments`. In the first three, keys are the type names you already use in data files (`Kill`, `CardSwipe`, `TimeOfDay`, `Economics`, …). `Chips` is keyed by chip kind, `Ui` covers the other uncategorized icons and `Monuments` is keyed by monument short name, each has its own section below.
Each entry has the same three fields:
| Field | Type | Default | Description |
| ------- | -------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Icon` | string \| null | `null` | Direct image URL for the icon. Null keeps the built-in icon. |
| `Color` | string \| null | `null` | Color for the icon, as hex (#rrggbb or #rrggbbaa), rgb() or rgba(). Null keeps the default color. Invalid colors warn at plugin load and render white. |
| `Raw` | boolean | `false` | Set true when the icon is a full-color image (a logo, custom art) so it renders in its own colors. When false the image renders as a solid color, which is right for monochrome icons. |
### Chips
[Section titled “Chips”](#chips)
A chip is the small pill in the objective display: an icon plus a short label, one per value.
| Key | Where | Built-in icon |
| ------------ | ------------------------------------------------- | ------------------ |
| `Preset` | Any `@preset` reference that doesn’t have an icon | Boxes |
| `Npc` | Scientists, tunnel dwellers and other human NPCs | Person |
| `Animal` | Bears, wolves, boars and the rest of the wildlife | Paw print |
| `Mount` | Vehicles and anything else a player rides | Car |
| `World` | Map objects that are neither built nor deployed | Box |
| `Structure` | Building blocks, walls, doors | Brick wall |
| `Deployable` | Player-placed deployables | Armchair |
| `Monument` | Monument chips | The monument’s own |
| `Unknown` | An entity name that has no built-in display name | Diamond |
### Ui
[Section titled “Ui”](#ui)
Other uncategorized icons, the ones that don’t belong to any objective, condition or reward type.
| Key | Where | Built-in icon |
| -------------- | ---------------------------------------------------------------------------- | -------------- |
| `Clock` | Category timers on contract and category cards and in the menu header | Clock |
| `Cooldown` | Objective cooldown rows and the HUD cooldown budget counter | Clock |
| `Burst` | The burst window indicator on the HUD and the objective row | Lightning bolt |
| `Lock` | The overlay on a locked objective’s icon | Lock |
| `Secret` | Hidden objectives in Progressive contracts | Question mark |
| `ItemFallback` | Item chips, item rewards and turn-in rows when an item name fails to resolve | Box |
| `Mixed` | The contract card icon when the contract mixes objective types | Asterisk |
| `Leaderboard` | The leaderboard button in the menu header | Podium |
| `Preferences` | The player preferences button in the menu header | Gear |
| `Prestige` | The prestige button in the menu header | Star |
| `MenuShortcut` | The [menu shortcut](#menu-shortcut) on the inventory screen | Hourglass |
### Monuments
[Section titled “Monuments”](#monuments)
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Per-monument overrides for monument chips. Keys are the **monument short names** you already type in [Monument conditions](/contracts/conditions/monument/), like `launch_site_1` or `oilrig_1` (case-insensitive). The config ships with every curated monument listed, all set to `null`.
Keys that aren’t in the shipped list work too, so you can give an icon to a custom map monument or a monument newer than the plugin’s curated list. Those render with a prettified name and the landmark icon by default.
## Menu
[Section titled “Menu”](#menu)
Behavior settings for the contracts menu.
| Field | Type | Default | Description |
| -------------------- | --------------- | ------- | ----------------------------------------------- |
| `Contracts Per Page` | integer (min 1) | `10` | How many contracts each page of the menu lists. |
The contract list (a category’s contracts, and the Active Contracts view) is split into pages of this size. When everything fits on one page, the pager bar at the bottom of the list is hidden entirely, so small categories look the same as if there were no pagination at all.
### Keep page sizes reasonable
[Section titled “Keep page sizes reasonable”](#keep-page-sizes-reasonable)
Every card on a page is sent and rendered at once, so a large value on a large category brings back the problem pagination solves. Rendering hundreds of cards in one go can freeze the player’s game for a moment when the menu opens, and can make the list render incorrectly. I don’t recommend going above 20 for this setting.
That being said, you should ideally avoid having a category that may trigger pagination at all (i.e. a category that allows more than 10 active contracts at a time). Having too many contracts active in one category defeats the purpose of Contracts’ fundamental design to have time-rotating quests and may cause a poor player experience, not a better one. [See why](https://www.rustcontracts.com/categories/#rotation:~:text=More%20choice%20is%20not%20more%20engagement)
## Menu Shortcut
[Section titled “Menu Shortcut”](#menu-shortcut)
A CONTRACTS button on the player’s inventory screen that opens the menu, so players don’t have to type `/contracts`. It sits in the top-left corner next to the game’s MISSIONS button and copies its look. It only shows while the inventory is open, and only to players with the `contracts.use` permission. Clicking it closes the inventory and opens the menu.
| Field | Type | Default | Description |
| ---------- | ------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Enabled` | boolean | `true` | Show a button on the inventory screen that opens the menu. Only players with the Use Contracts Features permission see it. |
| `Position` | string | `"163.5 17.5"` | Top-left corner of the button as "x y", counted from the top left of the screen with y going down. Units are pixels on a 1280x720 screen at UI scale 1.0, and the game resizes the button to match each player's own UI scale. The default sits right next to the game's MISSIONS button. |
| `Size` | string | `"70 20"` | Width and height of the button as "w h", in the same units as Position. |
Position and Size use the game’s own UI units: pixels on a 1280x720 screen at UI scale 1.0. The game resizes the button to match each player’s UI scale, so one position works for every player. The label is the `contracts.ui.menu_shortcut.label` lang key, and the icon is the `MenuShortcut` key under [Ui icons](#ui).
## Leaderboard
[Section titled “Leaderboard”](#leaderboard)
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Behavior settings for the [leaderboard](/leaderboard/).
| Field | Type | Default | Description |
| ------------------ | --------------- | ------- | ---------------------------------------------------- |
| `Enabled` | boolean | `true` | Show the leaderboard button in the menu. |
| `Players Per Page` | integer (min 1) | `10` | How many players each page of the leaderboard lists. |
## Prestige
[Section titled “Prestige”](#prestige)
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Behavior settings for the [prestige track](/prestige/).
| Field | Type | Default | Description |
| ---------------- | --------------- | ------- | ---------------------------------------------------- |
| `Tiers Per Page` | integer (min 1) | `25` | How many tiers each page of the prestige view lists. |
# Contracts
> What a contract is, the fields it carries, and how its objectives unlock.
A **contract** is a single quest: a small bundle of one or more [objectives](/contracts/objectives/) to complete and one or more [rewards](/contracts/rewards/) paid out when it is done. Contracts are stored in the `contracts_data.json` data file and surfaced to players by being listed in a [category](/categories/).
You author contracts with the [web editor](https://editor.rustcontracts.com). The shape below is what it reads and writes. For a hand-written, copy-pasteable example, see [Create your first contract](/getting-started/first-contract/).
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ----------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string (non-empty) | The name players see. Required. |
| `description` | string | A short blurb shown under the title. May be empty if the title says it all. |
| `objectives` | map (1 or more) | The goals to complete. See [Objectives](/contracts/objectives/). |
| `rewards` | map (1 or more) | What the contract pays out on completion. See [Rewards](/contracts/rewards/). |
| `progressionType` | enum: Independent/Sequential/Progressive | How the objectives unlock (see below). `Independent` is the default. |
| `replayCooldown` | number (1 or more) or `null` | How long a player waits after completing this contract before they can take it again, in seconds. `null` lets the category decide (see below). |
| `minPrestigeTier` | number (1 or more) or `null` | The lowest prestige tier a player needs before they can accept this contract, by tier number. `null` means no tier is needed. When the category also sets a tier, the higher one applies. |
| `costs` | map (0 or more) | What the player pays to accept the contract. Empty means it is free to take (see below). |
`objectives`, `rewards` and `costs` are stored as **keyed maps**, not arrays. Each key is a unique id you choose, and the key order is the display order. (Reordering in the editor rewrites this order.)
`minPrestigeTier` keeps the contract in the list but unavailable until the player has the tier. See [Locking contracts and categories](/prestige/#locking-contracts-and-categories). [⭐ Premium](/editions/ "Premium feature (see Editions)")
## Progression type
[Section titled “Progression type”](#progression-type)
`progressionType` controls the order players tackle a contract’s objectives:
* **`Independent`**: every objective is active at once. The default, and the right choice for most contracts.
* **`Sequential`**: objectives must be completed in order, so only the first incomplete one is active. Later objectives are visible but locked.
* **`Progressive`**: like `Sequential`, but locked objectives are **hidden** in the menu until they unlock, so the contract reveals itself step by step.
## The contract lifecycle
[Section titled “The contract lifecycle”](#the-contract-lifecycle)
A contract moves through these states for a given player:
* **Accepted**: the player opted into an available contract, and progress is now tracked.
* **Completed**: every objective is met and the reward is ready to be claimed.
* **Claimed**: the player collected their reward, before it is auto-claimed at the next rotation.
* **Abandoned**: the player dropped the contract before finishing it. This can happen to make room for new contracts when the max active contracts limit is reached.
* **Expired**: the contract’s category rotated while it was still in progress, so partial progress is lost (a clock decision, distinct from abandoning).
## Replayable contracts
[Section titled “Replayable contracts”](#replayable-contracts)
By default a contract is locked once completed and claimed, until the category rotates. A **replay cooldown** changes that. Set one and the player can take the same contract again after a wait.
Three fields decide the cooldown, one on the contract and two on the [category](/categories/):
| Field | Where | What it does |
| ------------------------ | -------- | --------------------------------------------------------------------------------------- |
| `replayCooldown` | contract | The wait for this contract, in seconds. |
| `replayCooldown` | category | The wait for every contract in the category that does not set its own. |
| `ignoreContractCooldown` | category | When on, the category’s wait is used for every contract in it, regardless of their own. |
A category with `ignoreContractCooldown` on and a `null` `replayCooldown` cancels any cooldown, whatever the contracts themselves say.
`0` means no wait at all. The player claims the reward and can take the contract again right away.
The wait starts at **completion**, not at the claim. A player who finishes a contract and claims an hour later has already burned an hour of the wait. They do still have to claim (or abandon) the previous run before taking it again, so no reward is ever lost.
### What players see
[Section titled “What players see”](#what-players-see)
While a contract cools down, the status chip on its card turns into a live countdown with a clock icon, It flips back to **Available** by itself when it hits zero. Open the contract and the details pane shows the Accept button greyed out with the time left under it.
## Pausable contracts
[Section titled “Pausable contracts”](#pausable-contracts)
[⭐ Premium](/editions/ "Premium feature (see Editions)")
A player can pause an in-progress contract instead of dropping it. A paused contract keeps everything it had and gives up the active slot it was using, so the player can take something else without losing progress.
How many contracts a player can keep paused is set by `Max Paused Contracts` in the [permissions config](/configuration/permissions/).
* A **paused** contract has two buttons: **Resume** and **Abandon**. It tracks no progress, cannot be turned in, and its objective rewards cannot be claimed while it is paused. Its card chip reads **PAUSED** in gray, and pausing unpins the contract from the HUD.
* **Resuming** needs a free active slot. It runs the same checks as accepting a contract, both the global limit and the category one, so a player at their cap has to claim, drop or pause something else first.
* **Abandoning** a paused contract works like abandoning any other contract.
* When a rotation ends, paused contracts are wiped along with everything else.
* Objective cooldown and burst windows run on real time and keep counting while a contract is paused. A contract paused for longer than the window comes back with a full cooldown again.
* This creates a small loophole where Playtime objectives with a cooldown can be paused while waiting for the cooldown to expire.
## Purchasable contracts
[Section titled “Purchasable contracts”](#purchasable-contracts)
[⭐ Premium](/editions/ "Premium feature (see Editions)")
A contract can ask the player to pay something to take it. This is ideal for high stakes contracts. It gives the player a reason to hurry and complete the contract before it expires, or risk losing their investment.
### Cost types
[Section titled “Cost types”](#cost-types)
| Type | What | Dependencies |
| --------------- | ------------------------- | ------------------------ |
| `Item` | A quantity of a Rust item | Nothing |
| `Economics` | In-game currency | The Economics plugin |
| `ServerRewards` | RP | The ServerRewards plugin |
```json
"costs": {
"entry_fee": { "type": "Economics", "amount": 500 },
"entry_scrap": { "type": "Item", "item": "scrap", "quantity": 100, "skinId": null }
}
```
#### Item
[Section titled “Item”](#item)
| Field | Type | Description |
| ---------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Item' |
| `item` | string | Rust item shortNames, lowercase dotted (e.g. rifle.ak, rifle.bolt, wood, stones, metal.fragments, scrap, hqm, lowgradefuel). |
| `quantity` | integer (min 1) | How many of the item the player pays. |
| `skinId` | integer \| null (min 0) | Only items carrying this workshop skin ID count as payment. null accepts any skin. |
#### Economics
[Section titled “Economics”](#economics)
| Field | Type | Description |
| -------- | ------ | ------------------------------------------------------------------------------ |
| `type` | string | Exactly 'Economics' |
| `amount` | number | How much Economics currency the player pays. May be a decimal. Greater than 0. |
#### Server Rewards
[Section titled “Server Rewards”](#server-rewards)
| Field | Type | Description |
| -------- | --------------- | ---------------------------------------------------- |
| `type` | string | Exactly 'ServerRewards' |
| `amount` | integer (min 1) | How many Server Rewards points (RP) the player pays. |
### What players see
[Section titled “What players see”](#what-players-see-1)
A contract with costs shows a price tag next to the reward icons on its card, and a **Cost** section in the details pane. Pressing Accept opens a confirm dialog instead of taking the contract right away. The dialog lists every cost, marks in red the ones the player cannot cover and by how much, and only lets them confirm when they can pay for all of it.
### Nothing is ever paid back
[Section titled “Nothing is ever paid back”](#nothing-is-ever-paid-back)
Once the player confirms, the payment is gone. There is no case where Contracts hands it back. E.g.:
* Abandoning the contract
* A category rotation ending mid-contract.
* Closing or deleting the category.
* A server wipe.
That is by design: the buy-in is what earns the contract a reward that big, so it has to be a real risk.
Completing the contract does not refund the payment. Add it to the rewards if you want the player to get it back.
### Skinned item costs
[Section titled “Skinned item costs”](#skinned-item-costs)
An item cost with a `skinId` only counts items carrying that exact skin. This is how you build a custom currency out of a skinned item. Leave `skinId` as `null` for any skin counts.
## What’s inside
[Section titled “What’s inside”](#whats-inside)
[Objectives](/contracts/objectives/)The goals to complete, one per type: kill, craft, gather, and so on.
[Conditions](/contracts/conditions/)Optional rules on an objective that restrict when progress counts.
[Rewards](/contracts/rewards/)What the contract pays out when finished.
# Conditions
> How conditions restrict when an objective's progress counts, and how to combine them.
A **condition** restricts *when* an [objective’s](/contracts/objectives/) progress is allowed to count. Conditions are optional. Without any, every qualifying action ticks the objective.
In reality, conditions are not special in themselves: they are just regular objective properties that have been abstracted out of objective-type-specific fields whenever I see these properties have the potential to apply to multiple current and future objective types. For example, instead of having all objective types have a `TimeOfDay` field that restricts progress to a certain time, the `TimeOfDayCondition` can be applied to any objective type to achieve the same effect with no additional implementation work needed from the objective type (even the ones that don’t exist yet!).
While any condition can be attached to any objective type, a condition only takes effect when the objective’s event actually provides the necessary data the condition needs. If it doesn’t, the condition is silently ignored and the objective progresses as if it weren’t there. The plugin won’t stop you from attaching conditions that don’t apply, so check that yours make sense for the objective. If a condition you attach isn’t working and you think it should, you can share it in the Discord server!
## Attaching conditions
[Section titled “Attaching conditions”](#attaching-conditions)
Conditions live in an objective’s `conditions` map. Progress ticks **only when the objective’s main requirement AND every attached condition are met**. For example, a [Kill](/contracts/objectives/kill/) objective with a [Time of Day](/contracts/conditions/time-of-day/) condition only counts kills made at night.
```json
{
"type": "Kill",
"amountRequired": 5,
"entities": ["animal_bear"],
"conditions": {
"at_night": {
"type": "TimeOfDay",
"startTime": "20:00",
"endTime": "06:00"
}
}
}
```
Each entry’s key is a unique id you choose. The web editor will generate one for you, but I recommend giving it a descriptive name so it’s easier to identify when debugging.
## Combining conditions
[Section titled “Combining conditions”](#combining-conditions)
Three **logical conditions** let you build more complex rules by nesting other conditions inside them. They can nest within each other to any depth.
### And
[Section titled “And”](#and)
Every nested condition must hold. Nested conditions go in a `conditions` map.
```json
{ "type": "And", "conditions": { "a": { "...": "" }, "b": { "...": "" } } }
```
### Or
[Section titled “Or”](#or)
At least one nested condition must hold.
```json
{ "type": "Or", "conditions": { "a": { "...": "" }, "b": { "...": "" } } }
```
### Not
[Section titled “Not”](#not)
Negates a **single** nested condition (note the singular `condition` key, not `conditions`).
```json
{ "type": "Not", "condition": { "type": "Weapon", "weapon": ["bow.hunting"] } }
```
Note
Attaching several conditions directly to an objective already behaves like **And**: they must all hold. Reach for an explicit `And`/`Or`/`Not` when you need to mix all-of and any-of logic, or to negate a rule.
## Overly flexible conditions
[Section titled “Overly flexible conditions”](#overly-flexible-conditions)
The flexibility that conditions give you is powerful. Perhaps it’s greatest feature is that it makes future objective types instantly augmentable and highly customizable from day one! However, this flexibility has one major drawback: in most cases, the plugin does not try to check that the combination of an objective type and one or more conditions actually makes sense. One of the most obvious example is the `Weapon` condition: although it can technically be attached to any objective type, it only really makes sense (and satisfies) for `Kill`, `Damage`, and `Gather` objectives. It doesn’t make sense for it to be attached to a `Craft` objective, because crafting never involves a weapon.
> Got a condition you expected to work with a certain objective type but it didn’t? Share it in the Discord server! Some conditions (especially `Weapon`) need a bit more explicit wiring to work with certain objective types, and your feedback helps improve compatibility.
## Condition types
[Section titled “Condition types”](#condition-types)
[Attachment](/contracts/conditions/attachment/)Only count progress when the weapon has specific attachments on it.
[Attack Distance](/contracts/conditions/attack-distance/)Only count progress within a range of distance to the target.
[Biome](/contracts/conditions/biome/)Only count progress while the player is in certain biomes.
[Hit Area](/contracts/conditions/hit-area/)Only count progress when the hit lands in specific body areas.
[Min Damage Ratio](/contracts/conditions/min-damage-ratio/)Only count progress when the player dealt a minimum share of the target's damage.
[Monument](/contracts/conditions/monument/)Only count progress while the player is inside certain monuments.
[Player Bleeding](/contracts/conditions/player-bleeding/)Only count progress while the player is bleeding.
[Player Building Privilege](/contracts/conditions/player-building-privilege/)Only count progress while the player's building privilege state matches.
[Player Calories](/contracts/conditions/player-calories/)Only count progress while the player's calories are within a range.
[Player Comfort](/contracts/conditions/player-comfort/)Only count progress while the player's comfort is within a range.
[Player Elevation](/contracts/conditions/player-elevation/)Only count progress while the player is above or below a certain height.
[Player Health](/contracts/conditions/player-health/)Only count progress while the player's health is within a range.
[Player Hydration](/contracts/conditions/player-hydration/)Only count progress while the player's hydration is within a range.
[Player Mount](/contracts/conditions/player-mount/)Only count progress while the player is on certain mounts.
[Player Movement](/contracts/conditions/player-movement/)Only count progress while the player is moving in certain ways.
[Player Radiation](/contracts/conditions/player-radiation/)Only count progress while the player is standing in a radiation zone.
[Player Radiation Poison](/contracts/conditions/player-radiation-poison/)Only count progress while the player is radiation-sick.
[Player Temperature](/contracts/conditions/player-temperature/)Only count progress while the player's core temperature is within a range.
[Player Wear](/contracts/conditions/player-wear/)Only count progress while the player is wearing (or not wearing) certain items.
[Player Wetness](/contracts/conditions/player-wetness/)Only count progress while the player's wetness is within a range.
[Player Workbench](/contracts/conditions/player-workbench/)Only count progress while the player is near a workbench of a certain level.
[Time of Day](/contracts/conditions/time-of-day/)Only count progress during a specific in-game time window.
[Weapon](/contracts/conditions/weapon/)Only count progress when using specific weapons.
[Weather](/contracts/conditions/weather/)Only count progress while certain weather is active at the player's position.
# Attachment
> Only count progress when the weapon has specific attachments on it.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only when the player’s weapon has one of the specified attachments on it.
```json
{
"type": "Attachment",
"attachments": ["weapon.mod.silencer"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------------- | --------- | --------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Attachment' |
| `attachments` | string\[] | Attachment item shortNames (e.g. weapon.mod.silencer, weapon.mod.holosight, weapon.mod.8x.scope). At least one entry. |
## Notes
[Section titled “Notes”](#notes)
* The weapon needs any of the listed attachments.
* Attachments are checked when the hit lands, not when the shot was fired.
* At least one listed attachment item must be valid or the condition is invalid.
* Useful when paired with a [Weapon](/contracts/conditions/weapon/). A Weapon condition with `rifle.ak` and an Attachment condition with `weapon.mod.holosight` only counts an AK with a holosight.
* To give each weapon its own attachment (an AK with a holosight OR a bolty with an 8x scope), put one Weapon and Attachment pair in each branch of an [Or](/contracts/conditions/#or) condition.
# Attack Distance
> Only count progress within a range of distance to the target.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only when the distance from the player to the target, in meters, is within the specified bounds.
```json
{
"type": "AttackDistance",
"minDistance": 100,
"maxDistance": -1
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------------- | ------ | --------------------------------------------------------------------- |
| `type` | string | Exactly 'AttackDistance' |
| `minDistance` | number | Minimum attack distance in meters. Inclusive. -1 disables this bound. |
| `maxDistance` | number | Maximum attack distance in meters. Inclusive. -1 disables this bound. |
## Notes
[Section titled “Notes”](#notes)
* Both bounds are **inclusive**, measured player-to-target in meters.
* `-1` disables a bound, so you can set only a minimum (long-range kills) or only a maximum (close-quarters), or both for a band.
* If both bounds are set, `minDistance` must not exceed `maxDistance`.
# Biome
> Only count progress while the player is in certain biomes.
Progress counts only when the player is in one of the specified biomes at the moment of the action.
```json
{
"type": "Biome",
"biomes": ["Arctic"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| -------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Biome' |
| `biomes` | enum\[]: Arid \| Temperate \| Tundra \| Arctic \| Jungle \| DeepSea | Biomes the player must be in. One of: Arid, Temperate, Tundra, Arctic, Jungle, DeepSea. A position's biome is its dominant biome: the one with the highest blend weight there, so a transition zone counts as exactly one biome. At least one entry is required, and an empty list is invalid because every position always has a dominant biome. |
## Notes
[Section titled “Notes”](#notes)
* **The player’s position is what matters**, at the instant of the action. A player standing in the Arctic gets no credit for a kill made in the Jungle. The target’s position is never considered.
* Biome names are the game’s own names: `Arid`, `Temperate`, `Tundra`, `Arctic`, `Jungle`, and `DeepSea`.
* A position’s biome is its **dominant biome**. Biomes blend into each other at their edges, so a spot in a transition zone technically belongs to several biomes at once, and the one with the highest weight wins. Every position always counts as exactly one biome.
* Biome membership is a **2D column** (i.e., like you would see from the map’s perspective): only the horizontal position matters. A player mining deep in a cave under the snow, or flying above it, is in the Arctic biome all the same.
* At least one biome is required. An empty list is invalid and the contract will not load: since every position always has a dominant biome, “any biome” would be a condition that never does anything.
* A biome the current map seed doesn’t have simply never matches, there is no validation. For example, not every map has a jungle. If you list only biomes absent from the map, the objective is never completable, so consider listing several biomes when that risk matters.
## Examples
[Section titled “Examples”](#examples)
Gather 10,000 sulfur ore in the Arctic:
```json
{
"type": "Gather",
"amountRequired": 10000,
"items": ["sulfur.ore"],
"conditions": {
"in_the_snow": {
"type": "Biome",
"biomes": ["Arctic"]
}
}
}
```
Kill 10 scientists in the Jungle:
```json
{
"type": "Kill",
"amountRequired": 10,
"entities": ["@scientists"],
"conditions": {
"in_the_jungle": {
"type": "Biome",
"biomes": ["Jungle"]
}
}
}
```
# Hit Area
> Only count progress when the hit lands in specific body areas.
Progress counts only when the hit lands in one of the specified body areas.
```json
{
"type": "HitArea",
"areas": ["Head"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'HitArea' |
| `areas` | enum\[]: Head \| Chest \| Stomach \| Arm \| Hand \| Leg \| Foot | Body areas that the hit must land in. One of: Head, Chest, Stomach, Arm, Hand, Leg, Foot. A hit qualifies when its area is any one of the listed areas. For a Kill objective, the area of the killing blow is used. For a Damage objective each hit is judged on its own area. At least one entry (hit area) is required. |
## Notes
[Section titled “Notes”](#notes)
* The seven areas are `Head`, `Chest`, `Stomach`, `Arm`, `Hand`, `Leg`, and `Foot`. A hit qualifies when its area is **any one** of the listed areas.
* On a **Kill** objective the area of the **killing blow** is used: the kill counts only when the attacker’s last hit on the victim landed in a selected area.
* On a **Damage** objective **each hit** is judged on its own area.
* Damage with no body area (fire, explosions, bleeding) resolves to no area and never satisfies the condition.
* The list must have at least one entry.
* Pair it with a [Kill](/contracts/objectives/kill/) objective for “kill with a headshot”, or a [Damage](/contracts/objectives/damage/) objective for “deal leg damage”.
# Min Damage Ratio
> Only count progress when the player dealt a minimum share of the target's damage.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only when the player’s share of the damage dealt to the target meets a minimum threshold. Use it to require that the player did most of the work, rather than tagging a target someone else fought.
```json
{
"type": "MinDamageRatio",
"minDamageRatio": 0.75
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ---------------- | --------------- | ------------------------------------------------------------- |
| `type` | string | Exactly 'MinDamageRatio' |
| `minDamageRatio` | number (0 to 1) | Fraction of target HP dealt in a single hit, between 0 and 1. |
## Notes
[Section titled “Notes”](#notes)
* The ratio is the **player’s total damage to the target divided by all damage the target took**: the player’s share, added up over the fight, not a single hit.
* `1.0` requires the player to have dealt **all** the damage (a true solo kill), and `0` accepts any contribution.
# Monument
> Only count progress while the player is inside certain monuments.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only when the player is inside the bounds of one of the specified monuments at the moment of the action.
```json
{
"type": "Monument",
"monuments": ["launch_site_1", "oilrig_1"]
}
```
Caution
This condition requires the free [Monument Finder](https://umod.org/plugins/monument-finder) plugin on the server. Contracts using it are **dependency-locked** while Monument Finder is not installed: they are never drawn into rotations, can’t be accepted, and stay invisible to players. Their data is untouched, so installing (or loading) Monument Finder makes them available again without a restart. Unloading it mid-rotation expires affected active contracts, except already claimed ones.
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ----------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Monument' |
| `monuments` | string\[] | The monuments the player must be inside for the condition to count. Each entry is matched case-insensitively against a monument's short name (such as 'launch\_site\_1' or 'oilrig\_1') or its Monument Finder alias (such as 'TrainStation'). An empty list means any monument counts. Preset references (entries starting with '@') are unpacked before matching. |
## Notes
[Section titled “Notes”](#notes)
* **The player’s position is what matters**, at the instant of the action. A player standing outside Launch Site gets no credit for killing a scientist inside it. The target’s position is never considered. This rule is the same for every objective type.
* Each entry is matched against a monument’s **short name** (like `launch_site_1` or `oilrig_1`) or its Monument Finder **alias** (like `TrainStation`, which covers every underground train station on the map). Custom monuments registered with Monument Finder work too.
* An empty list means any monument counts. This is useful for objectives that can be completed at any monument, but not outside of one.
* Entries starting with `@` are [preset](/presets/) references and are unpacked before matching, just like other objective type/condition.
* Monument bounds are Monument Finder’s curated bounds, used as-is. Refer to Monument Finder’s documentation for details on how they are defined and how to add custom bounds.
* A monument name that doesn’t exist on the current map simply never matches, there is no validation. Some smaller procedural maps don’t have all monuments, so a contract that requires one of those will not be excluded from rotations, but it will never be completable. I recommend that you use multiple monuments in your conditions, so that if one is missing on a map, the others can still be used.
## Examples
[Section titled “Examples”](#examples)
Kill 5 scientists at Launch Site:
```json
{
"type": "Kill",
"amountRequired": 5,
"entities": ["@scientists"],
"conditions": {
"at_launch_site": {
"type": "Monument",
"monuments": ["launch_site_1"]
}
}
}
```
Loot 5 gears on either oil rig:
```json
{
"type": "Loot",
"amountRequired": 5,
"items": ["gears"],
"conditions": {
"on_a_rig": {
"type": "Monument",
"monuments": ["oilrig_1", "oilrig_2"]
}
}
}
```
Kill 10 scientists at any monument:
```json
{
"type": "Kill",
"amountRequired": 10,
"entities": ["@scientists"],
"conditions": {
"at_any_monument": {
"type": "Monument",
"monuments": []
}
}
}
```
# Player Bleeding
> Only count progress while the player is bleeding.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only when the bleeding of the player performing the action is within the specified bounds at that instant. Bleeding is measured in HP the player still stands to lose, the same number the HUD indicator shows. It drains at a third of an HP per second until it runs out or a bandage clears it.
```json
{
"type": "PlayerBleeding",
"minBleeding": 10,
"maxBleeding": 50
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------------- | -------------- | --------------------------------------------------------------------------------- |
| `type` | string | Exactly 'PlayerBleeding' |
| `minBleeding` | number \| null | Minimum bleeding, in HP still to be lost. Null (or omitted) means no lower bound. |
| `maxBleeding` | number \| null | Maximum bleeding, in HP still to be lost. Null (or omitted) means no upper bound. |
## Notes
[Section titled “Notes”](#notes)
* The bleeding icon shows at **any value above 0**, so “while bleeding” is `minBleeding: 1`. `minBleeding: 20` means it only counts while the player still has 20 HP of bleeding left to suffer.
* Either bound can be `null` (or omitted entirely) for “no limit on that side”, but **at least one bound is required**. A condition with neither bound would always be satisfied, so it is invalid and the contract will not load.
* If `minBleeding` is greater than `maxBleeding`, the plugin swaps them and logs a warning.
## Examples
[Section titled “Examples”](#examples)
Kill 3 players while bleeding:
```json
{
"type": "Kill",
"amountRequired": 3,
"entities": ["player"],
"conditions": {
"bleeding": {
"type": "PlayerBleeding",
"minBleeding": 1
}
}
}
```
# Player Building Privilege
> Only count progress while the player's building privilege state matches.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only when the player’s building privilege state is one of the listed states.
```json
{
"type": "PlayerBuildingPrivilege",
"states": ["Authorized"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| -------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'PlayerBuildingPrivilege' |
| `states` | enum\[]: Authorized \| Blocked \| None | Building privilege states the player can be in. One of: Authorized, Blocked, None. Authorized means inside a privilege range and authorized on it. Blocked means inside a privilege range without authorization, the game's own 'building blocked'. None means outside every privilege range. Matching any listed state is enough. At least one entry is required, and an empty list is invalid because the three states cover every player. |
## States
[Section titled “States”](#states)
Every player is always in exactly one of the three states:
* **`Authorized`**: inside a privilege range and authorized on it.
* **`Blocked`**: inside a privilege range without authorization. This is the game’s own “building blocked” state.
* **`None`**: outside every privilege range. Nobody’s territory, not even your own.
Listing several states means **any one of them is enough**. Listing every state is permitted, though it’s a no-op that will always pass. At least one state must be listed.
## Recipes
[Section titled “Recipes”](#recipes)
* “In your own base” is `["Authorized"]`.
* “While raiding” or “in enemy territory” is `["Blocked"]`.
* “Out in the open” is `["None"]`.
* “Inside any base, yours or not” is `["Authorized", "Blocked"]`.
## Notes
[Section titled “Notes”](#notes)
* **The game’s own verdict decides.** Whatever grants building privilege counts: a tool cupboard, a tugboat’s vehicle privilege, and anything the game adds later. There is no filtering to tool cupboards only.
* `Blocked` and `None` are different things. A player two meters outside a stranger’s wall is `None`, one step inside the privilege radius they become `Blocked`. If you want “not at home” as a whole, list both.
* The list must have at least one entry. The three states cover every player, so an empty list would be an always-true no-op and the contract will not load.
## Examples
[Section titled “Examples”](#examples)
Kill 3 players while inside enemy territory:
```json
{
"type": "Kill",
"amountRequired": 3,
"entities": ["player"],
"conditions": {
"while_raiding": {
"type": "PlayerBuildingPrivilege",
"states": ["Blocked"]
}
}
}
```
Craft 10 medical syringes in the safety of your own base:
```json
{
"type": "Craft",
"amountRequired": 10,
"items": ["syringe.medical"],
"conditions": {
"at_home": {
"type": "PlayerBuildingPrivilege",
"states": ["Authorized"]
}
}
}
```
# Player Calories
> Only count progress while the player's calories are within a range.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only when the calories of the player performing the action are within the specified bounds at that instant. This is the food number on the vitals HUD, which runs from 0 to 500.
```json
{
"type": "PlayerCalories",
"maxCalories": 40
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------------- | -------------- | --------------------------------------------------------- |
| `type` | string | Exactly 'PlayerCalories' |
| `minCalories` | number \| null | Minimum calories. Null (or omitted) means no lower bound. |
| `maxCalories` | number \| null | Maximum calories. Null (or omitted) means no upper bound. |
## Notes
[Section titled “Notes”](#notes)
* The hungry warning shows at low calories, around `40` and below. Starvation damage starts at `20` or below. So “while hungry” is `maxCalories: 40`, “while starving” is `maxCalories: 20`.
* The abundance direction works too: “while well fed” is something like `minCalories: 400`.
* Either bound can be `null` (or omitted entirely) for “no limit on that side”, but **at least one bound is required**. A condition with neither bound would always be satisfied, so it is invalid and the contract will not load.
* If `minCalories` is greater than `maxCalories`, the plugin swaps them and logs a warning.
## Examples
[Section titled “Examples”](#examples)
Gather 500 wood while hungry:
```json
{
"type": "Gather",
"amountRequired": 500,
"items": ["wood"],
"conditions": {
"hungry": {
"type": "PlayerCalories",
"maxCalories": 40
}
}
}
```
# Player Comfort
> Only count progress while the player's comfort is within a range.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only when the comfort of the player performing the action is within the specified bounds at that instant. Bounds are the percent the vitals HUD shows, 0 to 100. Campfires and similar comfort sources raise it while the player stays nearby.
```json
{
"type": "PlayerComfort",
"minComfort": 50
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------------ | -------------- | -------------------------------------------------------------------------- |
| `type` | string | Exactly 'PlayerComfort' |
| `minComfort` | number \| null | Minimum comfort percent, 0 to 100. Null (or omitted) means no lower bound. |
| `maxComfort` | number \| null | Maximum comfort percent, 0 to 100. Null (or omitted) means no upper bound. |
## Notes
[Section titled “Notes”](#notes)
* Bounds are the **HUD percent**, not the game’s internal 0 to 1 fraction. This decision is by design to make it easier for you to author contracts based on what you would actually see in-game.
* The comfort icon shows at any value above 0. A campfire gives 50%, so “while comfortable” can be `minComfort: 50`, or `minComfort: 1` to accept any comfort source.
* Comfort chases its target at about 20% per second, so it takes a few seconds by the fire to qualify and a few seconds after leaving to drop.
* The hot warning (temperature above 40) forces comfort to drain, so “comfortable” and “overheating” do not mix.
* Either bound can be `null` (or omitted entirely) for “no limit on that side”, but **at least one bound is required**. A condition with neither bound would always be satisfied, so it is invalid and the contract will not load.
* If `minComfort` is greater than `maxComfort`, the plugin swaps them and logs a warning.
## Examples
[Section titled “Examples”](#examples)
Cook 20 bear meat while comfortable:
```json
{
"type": "Cook",
"amountRequired": 20,
"items": ["bearmeat.cooked"],
"conditions": {
"comfy": {
"type": "PlayerComfort",
"minComfort": 50
}
}
}
```
# Player Elevation
> Only count progress while the player is above or below a certain height.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only when the player’s vertical position, measured against a chosen baseline, is within the specified bounds at the moment of the action.
```json
{
"type": "PlayerElevation",
"relativeTo": "Ground",
"maxElevation": -5
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| -------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'PlayerElevation' |
| `relativeTo` | enum: SeaLevel \| Ground | Baseline the elevation is measured against. 'SeaLevel' is raw world height: 0 is the default sea level, positive is above it, negative below. 'Ground' is height relative to the terrain surface directly below the player: negative in caves and train tunnels, positive on rooftops or while flying. Over deep water the Ground baseline is the seabed, so a swimmer at the surface reads as far above ground. Defaults to 'SeaLevel' when omitted. |
| `minElevation` | number \| null | Minimum elevation in meters, any sign. Null (or omitted) means no lower bound. |
| `maxElevation` | number \| null | Maximum elevation in meters, any sign. Null (or omitted) means no upper bound. |
## Baselines
[Section titled “Baselines”](#baselines)
`relativeTo` picks what elevation is measured against:
* **`SeaLevel`** (default): raw world height. `0` is the default sea level, positive values are above it, negative values below. Use it for absolute heights, like “while flying above 200 meters” or “while diving below -10 meters”.
* **`Ground`**: height relative to the terrain surface directly below the player. Negative in caves and train tunnels, positive on rooftops or while flying. Use it for “underground” or “high above the ground” rules regardless of where on the map the player is.
## Notes
[Section titled “Notes”](#notes)
* **The player’s position is what matters**, at the instant of the action. A player on top of a mountain shooting a target in a valley is at the mountain’s elevation. The target’s position is never considered.
* Bounds are in meters and **negative values are accepted** on both baselines: below sea level for `SeaLevel`, underground for `Ground`.
* Either bound can be `null` (or omitted entirely) for “no limit on that side”, but **at least one bound is required**. A condition with neither bound would always be satisfied, so it is invalid and the contract will not load.
* If `minElevation` is greater than `maxElevation`, the plugin swaps them and logs a warning.
* `Ground` is the raw difference between the player and the terrain surface, with no special handling for water: over deep ocean the baseline is the seabed, so a swimmer at the surface reads as far above ground. For “in the water” style rules, prefer `SeaLevel` with a negative bound.
## Examples
[Section titled “Examples”](#examples)
Gather 500 stones underground (caves, train tunnels):
```json
{
"type": "Gather",
"amountRequired": 500,
"items": ["stones"],
"conditions": {
"underground": {
"type": "PlayerElevation",
"relativeTo": "Ground",
"maxElevation": -5
}
}
}
```
Kill 10 scientists from high ground:
```json
{
"type": "Kill",
"amountRequired": 10,
"entities": ["@scientists"],
"conditions": {
"high_ground": {
"type": "PlayerElevation",
"relativeTo": "SeaLevel",
"minElevation": 100
}
}
}
```
# Player Health
> Only count progress while the player's health is within a range.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only when the health of the player performing the action is within the specified bounds at that instant.
```json
{
"type": "PlayerHealth",
"maxHealth": 25
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ----------- | ---------------------- | ----------------------------------------------------------------------------------- |
| `type` | string | Exactly 'PlayerHealth' |
| `minHealth` | number \| null (min 0) | Minimum player HP (raw HP, NOT a fraction). Null (or omitted) means no lower bound. |
| `maxHealth` | number \| null (min 0) | Maximum player HP (raw HP, NOT a fraction). Null (or omitted) means no upper bound. |
## Notes
[Section titled “Notes”](#notes)
* Bounds are **raw HP, not a percentage**: `50` means 50 health points, not 50%. A full-health player is at `100` unless something raised their max health.
* Either bound can be `null` (or omitted entirely) for “no limit on that side”, but **at least one bound is required**. A condition with neither bound would always be satisfied, so it is invalid and the contract will not load.
* Set only `maxHealth` for a “while wounded” rule, or only `minHealth` for a “while healthy” rule.
* If `minHealth` is greater than `maxHealth`, the plugin swaps them and logs a warning.
* Configs from before 1.8.0 used `-1` to disable a bound. The plugin still reads a negative bound as “no bound”, and importing an old file into the editor rewrites it to `null` automatically.
## Examples
[Section titled “Examples”](#examples)
Kill 5 players while below 25 HP:
```json
{
"type": "Kill",
"amountRequired": 5,
"entities": ["player"],
"conditions": {
"wounded": {
"type": "PlayerHealth",
"maxHealth": 25
}
}
}
```
# Player Hydration
> Only count progress while the player's hydration is within a range.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only when the hydration of the player performing the action is within the specified bounds at that instant. This is the water number on the vitals HUD, which runs from 0 to 250.
```json
{
"type": "PlayerHydration",
"maxHydration": 25
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| -------------- | -------------- | ---------------------------------------------------------- |
| `type` | string | Exactly 'PlayerHydration' |
| `minHydration` | number \| null | Minimum hydration. Null (or omitted) means no lower bound. |
| `maxHydration` | number \| null | Maximum hydration. Null (or omitted) means no upper bound. |
## Notes
[Section titled “Notes”](#notes)
* The thirsty warning shows at low hydration, around `25` and below. Dehydration damage starts at `20` or below, and sprinting locks at exactly `0`. So “while thirsty” is `maxHydration: 25`.
* The abundance direction works too: “while fully hydrated” is something like `minHydration: 200`.
* Running hot (temperature above 40) drains hydration, so desert-themed contracts can combine this with [Player Temperature](/contracts/conditions/player-temperature/).
* Either bound can be `null` (or omitted entirely) for “no limit on that side”, but **at least one bound is required**. A condition with neither bound would always be satisfied, so it is invalid and the contract will not load.
* If `minHydration` is greater than `maxHydration`, the plugin swaps them and logs a warning.
## Examples
[Section titled “Examples”](#examples)
Kill 5 scientists while thirsty:
```json
{
"type": "Kill",
"amountRequired": 5,
"entities": ["scientistnpc_full_any"],
"conditions": {
"thirsty": {
"type": "PlayerHydration",
"maxHydration": 25
}
}
}
```
# Player Mount
> Only count progress while the player is on certain mounts.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only when the player is mounted on one of the specified mounts, or on anything at all when the list is empty.
```json
{
"type": "PlayerMount",
"mounts": ["horse", "minicopter.entity"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'PlayerMount' |
| `mounts` | string\[] | The entity prefab names of the mounts the player can be on for the condition to count. An empty list means mounted on anything. |
## Notes
[Section titled “Notes”](#notes)
* Mounts are matched by **vehicle prefab name**. The player counts as mounted when seated on any one of the listed mounts.
* **An empty list means any mount.** The player still has to be mounted on something, so “kill 2 scientists while mounted” is this condition with `"mounts": []`.
* A deployed parachute mounts the player, so “while parachuting” is this condition with the parachute entity:
```json
{
"type": "PlayerMount",
"mounts": ["parachute"]
}
```
# Player Movement
> Only count progress while the player is moving in certain ways.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only when the player is moving in one of the specified ways at the instant of the action.
```json
{
"type": "PlayerMovement",
"states": ["OnFoot", "Sprinting"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| -------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'PlayerMovement' |
| `states` | enum\[]: OnFoot \| Swimming \| Falling \| Sprinting \| Ducking \| OnLadder | Movement states the player can be in. One of: OnFoot, Swimming, Falling, Sprinting, Ducking, OnLadder. Matching any listed state is enough. States overlap rather than exclude each other: a sprinting player is also OnFoot, and a ducking player on the ground is both Ducking and OnFoot. At least one entry is required, and an empty list is invalid. |
## States
[Section titled “States”](#states)
* **`OnFoot`**: grounded, unmounted, and not swimming.
* **`Swimming`**: in water deep enough to swim.
* **`Falling`**: airborne **and descending**. Off the ground with no support and moving downward. A rising jump is not falling, but the falling part of a jump may count in some situations (e.g: unleveled ground).
* **`Sprinting`**: running at full speed.
* **`Ducking`**: crouched.
* **`OnLadder`**: climbing a ladder.
States overlap rather than exclude each other: a sprinting player is also `OnFoot`, and a crouched player on the ground is both `Ducking` and `OnFoot`. Listing several states means **any one of them is enough**. To demand exclusivity, combine with [Not](/contracts/conditions/#not): “on foot but not sprinting” is two conditions: 1 PlayerMovement condition with `OnFoot` + 1 `Not` condition with a `Sprinting` movement condition.
## Notes
[Section titled “Notes”](#notes)
* **A mounted player is in no movement state.** Riding a horse, sitting in a vehicle, or hanging from a deployed parachute never satisfies this condition. For “while mounted” rules, use the [Player Mount condition](/contracts/conditions/player-mount/) instead. The two compose cleanly: “by horse” is Player Mount, “on foot” is Player Movement, and they never both match.
* A player standing on the deck of a moving vessel is grounded and unmounted, so they read as `OnFoot` even though the boat is doing the traveling.
* The list must have at least one entry. An empty list would match nothing useful, so it is invalid and the contract will not load.
## Examples
[Section titled “Examples”](#examples)
Travel 500 meters on foot:
```json
{
"type": "Travel",
"amountRequired": 500,
"conditions": {
"on_foot": {
"type": "PlayerMovement",
"states": ["OnFoot"]
}
}
}
```
Kill 3 players while falling:
```json
{
"type": "Kill",
"amountRequired": 3,
"entities": ["player"],
"conditions": {
"while_falling": {
"type": "PlayerMovement",
"states": ["Falling"]
}
}
}
```
# Player Radiation
> Only count progress while the player is standing in a radiation zone.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only when the radiation level of the spot the player is standing on is within the specified bounds at that instant. This is the raw zone value in rads, before clothing protection, so a hazmat suit does not lower it. Step out of the zone and it drops back to 0.
This condition is about **where the player is standing right now**. For the sickness that lingers after leaving, use [Player Radiation Poison](/contracts/conditions/player-radiation-poison/).
```json
{
"type": "PlayerRadiation",
"minRadiation": 1
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| -------------- | -------------- | ----------------------------------------------------------------------- |
| `type` | string | Exactly 'PlayerRadiation' |
| `minRadiation` | number \| null | Minimum zone radiation in rads. Null (or omitted) means no lower bound. |
| `maxRadiation` | number \| null | Maximum zone radiation in rads. Null (or omitted) means no upper bound. |
## Notes
[Section titled “Notes”](#notes)
* Monument radiation zones come in four tiers producing roughly `2`, `10`, `25` and `51` rads. Zones also fade toward their edges, so the value dips near the border.
* The game never shows this number on screen. Players can gauge it from the monument tier and from whether their protection silences the radiation warning or not.
* “While standing in radiation” is `minRadiation: 1`. Push it to `minRadiation: 25` to force the player deep into high tier zones.
* Gear does not matter. A player in a full rad suit still counts, and so does a naked one. If you want to punish exposure instead, use [Player Radiation Poison](/contracts/conditions/player-radiation-poison/), which does respect protection.
* The zone counts even when the server has radiation damage disabled.
* Either bound can be `null` (or omitted entirely) for “no limit on that side”, but **at least one bound is required**. A condition with neither bound would always be satisfied, so it is invalid and the contract will not load.
* If `minRadiation` is greater than `maxRadiation`, the plugin swaps them and logs a warning.
## Examples
[Section titled “Examples”](#examples)
Loot 100 scrap while inside a radiation zone:
```json
{
"type": "Loot",
"amountRequired": 100,
"items": ["scrap"],
"conditions": {
"irradiated": {
"type": "PlayerRadiation",
"minRadiation": 1
}
}
}
```
# Player Radiation Poison
> Only count progress while the player is radiation-sick.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only when the accumulated radiation poisoning of the player performing the action is within the specified bounds at that instant. Poisoning is the radiation sickness number on the HUD. It builds up while exposed, sticks around after leaving the zone, and drains slowly while it deals damage.
This condition is about **lingering sickness**. To require standing in a radiation zone right now, use [Player Radiation](/contracts/conditions/player-radiation/).
```json
{
"type": "PlayerRadiationPoison",
"minRadiationPoison": 25
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| -------------------- | -------------- | -------------------------------------------------------------------- |
| `type` | string | Exactly 'PlayerRadiationPoison' |
| `minRadiationPoison` | number \| null | Minimum radiation poisoning. Null (or omitted) means no lower bound. |
| `maxRadiationPoison` | number \| null | Maximum radiation poisoning. Null (or omitted) means no upper bound. |
## Notes
[Section titled “Notes”](#notes)
* Poisoning gains the player’s current exposure per second while irradiated, and clothing protection subtracts from that intake. Unlike [Player Radiation](/contracts/conditions/player-radiation/), gear matters here: a minute naked in a 25 rad zone leaves a sizeable dose, a rad suit shrugs it off.
* “While radiation-sick” is `minRadiationPoison: 1`. At `25` and above the player’s passive health regeneration has stopped, and at `100` and above sprinting is disabled.
* Drinking water, radpills, anti-radiation tea and pies reduce it, and swimming while wet drains it a little faster.
* Either bound can be `null` (or omitted entirely) for “no limit on that side”, but **at least one bound is required**. A condition with neither bound would always be satisfied, so it is invalid and the contract will not load.
* If `minRadiationPoison` is greater than `maxRadiationPoison`, the plugin swaps them and logs a warning.
## Examples
[Section titled “Examples”](#examples)
Kill 5 scientists while radiation-sick:
```json
{
"type": "Kill",
"amountRequired": 5,
"entities": ["scientistnpc_full_any"],
"conditions": {
"radiation_sick": {
"type": "PlayerRadiationPoison",
"minRadiationPoison": 25
}
}
}
```
# Player Temperature
> Only count progress while the player's core temperature is within a range.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only when the core temperature of the player performing the action is within the specified bounds at that instant. This is the temperature number on the vitals HUD, in degrees Celsius, so players can check their own screen against the contract’s requirement.
```json
{
"type": "PlayerTemperature",
"maxTemperature": 5
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ---------------- | -------------- | ---------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'PlayerTemperature' |
| `minTemperature` | number \| null | Minimum core temperature in degrees Celsius, any sign. Null (or omitted) means no lower bound. |
| `maxTemperature` | number \| null | Maximum core temperature in degrees Celsius, any sign. Null (or omitted) means no upper bound. |
## Warning thresholds
[Section titled “Warning thresholds”](#warning-thresholds)
The game’s own HUD warnings sit at fixed temperatures, which makes good anchors for bounds:
* **Cold** warning shows below `5`.
* **Freezing** damage starts below `1`, and gets worse below `-10` and `-20`.
* **Hot** warning shows above `40`. Hydration also starts draining there.
* **Overheating** damage starts above `60`.
So “while cold” is `maxTemperature: 5`, “while freezing” is `maxTemperature: 1`, and “while too hot” is `minTemperature: 40`.
## Notes
[Section titled “Notes”](#notes)
* Bounds accept any sign. Deep cold biomes and swimming push temperature well below zero.
* Either bound can be `null` (or omitted entirely) for “no limit on that side”, but **at least one bound is required**. A condition with neither bound would always be satisfied, so it is invalid and the contract will not load.
* If `minTemperature` is greater than `maxTemperature`, the plugin swaps them and logs a warning.
* Being wet drags core temperature down hard. A cold biome plus a swim is the fastest way to freeze, which pairs well with a [Biome](/contracts/conditions/biome/) condition for arctic-themed contracts.
## Examples
[Section titled “Examples”](#examples)
Kill 3 players while freezing:
```json
{
"type": "Kill",
"amountRequired": 3,
"entities": ["player"],
"conditions": {
"freezing": {
"type": "PlayerTemperature",
"maxTemperature": 1
}
}
}
```
# Player Wear
> Only count progress while the player is wearing (or not wearing) certain items.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only when the player’s worn items match the rule, either wearing specified gear, or wearing nothing at all.
```json
{
"type": "PlayerWear",
"items": ["hazmatsuit"],
"requireAll": false,
"requireNaked": false
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| -------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'PlayerWear' |
| `items` | string\[] | Rust item shortNames, lowercase dotted (e.g. mask.bandana, shoes.boots, metal.plate.torso, ballistic.helmet). Add :skin with the skin's workshop ID to match only that skinned item (paper:3731371060). A bare short name matches any skin, and :0 matches only the unskinned item. |
| `requireAll` | boolean | If true, the player must be wearing every listed item to qualify. If false, any one suffices. |
| `requireNaked` | boolean | If true, the player must be wearing nothing. Leave items empty in this case. Mutually exclusive with non-empty items. |
## Notes
[Section titled “Notes”](#notes)
* Listed items must be **wearable**: non-wearable shortnames are dropped when the config loads.
* `requireAll` toggles between all-of (`true`) and any-of (`false`) matching of the `items` list.
* `requireNaked` is the opposite mode: it requires an **empty** `items` list and matches only when the player wears nothing.
Caution
`items` and `requireNaked` are mutually exclusive: set items **or** require naked, not both. A “wear everything” rule (`requireAll: true`) with more than the **7** wearable slots can never be satisfied.
# Player Wetness
> Only count progress while the player's wetness is within a range.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only when the wetness of the player performing the action is within the specified bounds at that instant. Bounds are the percent the vitals HUD shows, 0 to 100.
```json
{
"type": "PlayerWetness",
"minWetness": 5
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------------ | -------------- | -------------------------------------------------------------------------- |
| `type` | string | Exactly 'PlayerWetness' |
| `minWetness` | number \| null | Minimum wetness percent, 0 to 100. Null (or omitted) means no lower bound. |
| `maxWetness` | number \| null | Maximum wetness percent, 0 to 100. Null (or omitted) means no upper bound. |
## Notes
[Section titled “Notes”](#notes)
* Bounds are the **HUD percent**, not the game’s internal 0 to 1 fraction. `minWetness: 50` matches a player whose HUD reads 50% wet or more. This decision is by design to make it easier for you to author contracts based on what you would actually see in-game.
* “While wet” is `minWetness: 5`, low enough to catch rain but not a stale splash. Swimming pushes wetness far higher, so “while swimming” flavor works with `minWetness: 80`.
* Being wet drags core temperature down, so this pairs naturally with [Player Temperature](/contracts/conditions/player-temperature/) for freezing-swimmer contracts.
* Either bound can be `null` (or omitted entirely) for “no limit on that side”, but **at least one bound is required**. A condition with neither bound would always be satisfied, so it is invalid and the contract will not load.
* If `minWetness` is greater than `maxWetness`, the plugin swaps them and logs a warning.
## Examples
[Section titled “Examples”](#examples)
Catch 5 fish while soaked:
```json
{
"type": "Fishing",
"amountRequired": 5,
"conditions": {
"soaked": {
"type": "PlayerWetness",
"minWetness": 80
}
}
}
```
# Player Workbench
> Only count progress while the player is near a workbench of a certain level.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only when the player’s craft level is within the specified bounds at the moment of the action. Craft level is the workbench benefit the game itself tracks: 0 with no workbench in range, otherwise the highest level (1 to 3) among the workbenches the player is standing at.
```json
{
"type": "PlayerWorkbench",
"minLevel": 2
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ---------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'PlayerWorkbench' |
| `minLevel` | integer \| null (0 to 3) | Minimum craft level, 0 to 3. Follows the game's crafting rule, so a higher tier covers a lower requirement: a minLevel of 2 is satisfied at a level 2 or level 3 workbench. Null (or omitted) means no lower bound. |
| `maxLevel` | integer \| null (0 to 3) | Maximum craft level, 0 to 3. A maxLevel of 0 requires the player to have no tier workbench in range at all. Null (or omitted) means no upper bound. |
## Notes
[Section titled “Notes”](#notes)
* **The condition tests the instant the progress lands.** On Craft objectives that is when the item **finishes** crafting, not when it is queued. A player who queues items at a bench and walks away gets no progress, they need to still be at the bench when each item completes. This is because the craft objective credits progress when the item finishes crafting, so that’s also when conditions are checked.
* Bounds follow the game’s crafting rule: a higher level bench covers a lower requirement, so `minLevel: 2` is satisfied at a level 2 or level 3 workbench.
* Range is the game’s own workbench radius, the same one that unlocks bench-locked recipes. There is no custom distance check. It should work with other plugins that change the workbench radius. As long as you can see the visual workbench indicator on your HUD, the condition will be satisfied.
* `maxLevel: 0` flips the condition around: the player must have **no workbench in range** at all.
* Either bound can be `null` (or omitted entirely) for “no limit on that side”, but **at least one bound is required**. A condition with neither bound would always be satisfied, so it is invalid and the contract will not load.
* If `minLevel` is greater than `maxLevel`, the plugin swaps them and logs a warning. Values outside 0 to 3 are clamped into range.
## Examples
[Section titled “Examples”](#examples)
Craft bandages at a level 2 workbench or better:
```json
{
"type": "Craft",
"amountRequired": 10,
"items": ["bandage"],
"conditions": {
"at_workbench": {
"type": "PlayerWorkbench",
"minLevel": 2
}
}
}
```
Craft stone tools out in the wild, away from any workbench:
```json
{
"type": "Craft",
"amountRequired": 5,
"items": ["stonehatchet", "stone.pickaxe"],
"conditions": {
"no_workbench": {
"type": "PlayerWorkbench",
"maxLevel": 0
}
}
}
```
# Time of Day
> Only count progress during a specific in-game time window.
Progress counts only when the in-game time of day is within the specified window.
```json
{
"type": "TimeOfDay",
"startTime": "20:00",
"endTime": "06:00"
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ----------- | ------ | ------------------------------------------------------------------------- |
| `type` | string | Exactly 'TimeOfDay' |
| `startTime` | string | Time of day in 24-hour HH:MM format (e.g. 20:00). |
| `endTime` | string | Time of day in 24-hour HH:MM format (e.g. 04:00). May wrap past midnight. |
## Notes
[Section titled “Notes”](#notes)
* The window is inclusive of `startTime` and `endTime`.
* The window **may wrap past midnight**: `20:00` to `06:00` means “from 8pm until 6am the next morning”.
# Weapon
> Only count progress when using specific weapons.
Progress counts only when the player is using one of the specified weapons.
```json
{
"type": "Weapon",
"weapon": ["rifle.bolt"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| -------- | --------- | ---------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Weapon' |
| `weapon` | string\[] | Weapon item shortNames (e.g. rifle.ak, rifle.bolt, pistol.semiauto). At least one entry. |
## Notes
[Section titled “Notes”](#notes)
* The player must be using one of the listed weapons for the action to count.
* On a [Gather](/contracts/objectives/gather/) objective the “weapon used” is approximated by the player’s **currently held item**, because Rust doesn’t report which tool did the gathering.
* At least one listed shortname must resolve to a real item, or the condition is treated as invalid.
* Only a held weapon carries a skin. Hits from rockets, thrown explosives, arrows and fire always count as unskinned.
# Weather
> Only count progress while certain weather is active at the player's position.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Progress counts only while one of the listed weather states is active at the position of the player performing the action.
```json
{
"type": "Weather",
"weathers": ["Rain", "Thunder"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ---------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Weather' |
| `weathers` | enum\[]: Clear \| Rain \| Snow \| Fog \| Thunder | Weather states that count. One of: Clear, Rain, Snow, Fog, Thunder. The condition is satisfied while any listed state is active, so \['Rain', 'Thunder'] reads as 'in the rain or during a thunderstorm'. Clear means none of the other four states are active. At least one entry is required, an empty list is invalid. |
## Notes
[Section titled “Notes”](#notes)
* **Any listed state matches.** `["Rain", "Thunder"]` reads as “in the rain or during a thunderstorm”. For “only in rain that is not a thunderstorm”, combine with [Not](/contracts/conditions/#not).
* Weather is read **at the player’s position**, not globally. The game shapes weather by terrain: deserts get about half the rain, and in the Arctic rain falls as snow instead. The same storm can be `Rain` in the forest and `Snow` on the ice sheet, and a player in the desert may not match `Rain` while the rest of the map does. Since snow only falls in the Arctic, `Snow` pairs naturally with [Biome](/contracts/conditions/biome/) conditions.
* The condition reads the **sky, not the player**. A player under a roof during a storm still matches `Rain`. If you want “actually got wet”, that is [Player Wetness](/contracts/conditions/player-wetness/)’s job, and the two compose: rain plus a low wetness bound makes a “fish in the rain while staying dry” contract.
* Thresholds are built in and anchored to the game’s own numbers: any rain at all counts as `Rain`, any snow as `Snow`, `Fog` needs fog thick enough to matter, and `Thunder` matches whenever the game reports a thunderstorm. There is no intensity config.
* `Clear` means **none of the other four** states are active at the player’s position.
* At least one state is required. An empty list is invalid and the contract will not load.
* Weather rolls to a new pattern roughly every 18 in-game hours, and servers get a stretch of clear skies right after a wipe. A short-lived contract can easily miss the weather it asks for, so weather-gated contracts fit daily or weekly rotations much better than hourly ones. The sibling environmental condition, [Time of Day](/contracts/conditions/time-of-day/), has the same kind of timing caveat.
## Examples
[Section titled “Examples”](#examples)
Catch 5 fish in the rain:
```json
{
"type": "Fishing",
"amountRequired": 5,
"conditions": {
"in_the_rain": {
"type": "Weather",
"weathers": ["Rain"]
}
}
}
```
Kill 3 scientists during a thunderstorm:
```json
{
"type": "Kill",
"amountRequired": 3,
"entities": ["@scientists"],
"conditions": {
"during_a_storm": {
"type": "Weather",
"weathers": ["Thunder"]
}
}
}
```
# Objectives
> What objectives are, the fields every objective shares, and how each type counts progress.
An **objective** is a single goal inside a contract: kill an animal, craft an item, hack a crate. A contract needs at least one. The `type` field selects which objective it is and which fields it carries. Each per-type page in this section lists its complete field set, shared and type-specific alike.
Every objective shares `title`, `description` and a `conditions` map (a keyed map, not an array) of optional [conditions](/contracts/conditions/) that restrict *when* progress counts. Progress ticks only when the objective’s main requirement **and every** attached condition are met.
Objectives also share an optional `hideDetails` flag (default `false`). The menu normally renders an objective’s fields and conditions for players (the [objective display](/contracts/objectives/display/)). Setting `hideDetails: true` suppresses that for the objective, leaving your title and description as the only explanation players get.
Technical note
At the moment, all objective types are amount-based, meaning they require a certain quantity of something to be completed and have a `amountRequired` field. That being said, that field is not anchored into the base shape of an objective, it just so happens that all objective types currently derive from an abstract `AmountObjective` type. It’s also why the docs don’t assume that `amountRequired` is a base field, like `title` and `description` are. The objective shape is designed to accommodate other types of non-amount-based objectives in the future, should one surface.
## Item and entity lists
[Section titled “Item and entity lists”](#item-and-entity-lists)
Fields that take items (`items`, `item`, `fish`, `bait`) or entities (`entities`) use Rust **shortnames** / prefab names, lowercase: `rifle.ak`, `scrap`, `animal_bear`, not display names. One exception: the `entities` list on [Kill](/contracts/objectives/kill/) and [Damage](/contracts/objectives/damage/) also matches the display names of custom NPCs from plugins like BetterNpc or NpcSpawn.
Any item/entity list also accepts **preset references** written as `@presetName`. At runtime the plugin expands the reference into that preset’s list, so you can define a list (e.g. all rifles) once and reuse it. Invalid shortnames are dropped when the config loads.
An item name can also carry a skin, as `paper:3731371060`, to match only a custom item. See [Targeting custom items](/guides/custom-items/).
## Objective conditions
[Section titled “Objective conditions”](#objective-conditions)
Contracts’ strength comes from the versatility and possibilities of how objectives can be augmented with conditions. The same objective with no condition can be given a whole new flavor and level of difficulty when you add conditions to it.
[Conditions](/contracts/conditions/)Optional rules that restrict when progress counts, for example 'only at night', or 'only with a specific weapon'.
## Pacing
[Section titled “Pacing”](#pacing)
Amount-based objectives can optionally shape how progress adds up over time:
[Cooldown](/contracts/objectives/cooldown/)Limit how fast progress adds up: at most X units in any sliding window.
[Burst](/contracts/objectives/burst/)Require actions to arrive clustered: X units within one window form a burst, and the objective counts bursts.
## Objective types
[Section titled “Objective types”](#objective-types)
[Card Swipe](/contracts/objectives/card-swipe/)Swipe puzzle keycards in monument card readers.
[Cloning](/contracts/objectives/cloning/)Take clones (cuttings) from specified plants.
[Cook](/contracts/objectives/cook/)Smelt, refine or cook specified items in ovens.
[Craft](/contracts/objectives/craft/)Create specified items.
[Crate Hack](/contracts/objectives/crate-hack/)Hack hackable locked crates.
[Damage](/contracts/objectives/damage/)Inflict damage on specified entities.
[Dig](/contracts/objectives/dig/)Dig up metal detector spots, optionally only ones that turn up specified items.
[Fishing](/contracts/objectives/fishing/)Catch specified fish using specific bait.
[Gamble](/contracts/objectives/gamble/)Wager scrap at Rust's gambling games, counting bets won, lost, played, or the scrap they moved.
[Gather](/contracts/objectives/gather/)Collect items from dispensers, like trees and ore nodes.
[Harvest](/contracts/objectives/harvest/)Harvest plants from planters or the world.
[Heal](/contracts/objectives/heal/)Restore an amount of health.
[Kill](/contracts/objectives/kill/)Eliminate specified entities.
[Loot](/contracts/objectives/loot/)Obtain items from the world or world containers.
[Mix](/contracts/objectives/mix/)Mix specified items at a mixing table or cooking workbench.
[Plant](/contracts/objectives/plant/)Plant specified seeds.
[Playtime](/contracts/objectives/playtime/)Spend time connected, alive and awake, measured in minutes of play.
[Recycle](/contracts/objectives/recycle/)Recycle specified items, counting either what goes into the recycler or what comes out.
[Repair](/contracts/objectives/repair/)Repair items at a bench, or restore HP to structures, deployables and vehicles with the hammer.
[Travel](/contracts/objectives/travel/)Cover distance across the world by any means, measured in meters of 3D displacement.
[Turn In](/contracts/objectives/turn-in/)Submit items to the plugin.
[Visit](/contracts/objectives/visit/)Arrive at monuments. Each arrival from fully outside counts once.
# Burst
> Require actions to arrive clustered, counting bursts instead of raw actions.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
The optional `burst` field requires an objective’s actions to arrive **clustered**: at least `amount` units within one trailing window of `windowSeconds` form one **burst**. `amountRequired` then counts **completed bursts** instead of raw actions. When the field is absent, every action counts individually.
Burst is the inverse of [cooldown](/contracts/objectives/cooldown/): cooldown ignores actions that come too fast, burst ignores actions that come too slow.
Like cooldown, burst is available on every **amount-based** objective (see the technical note on the [Objectives overview](/contracts/objectives/)).
```json
{
"type": "Kill",
"title": "Double Tap",
"description": "Kill 2 scientists within 3 seconds of each other, 5 times.",
"amountRequired": 5,
"burst": { "amount": 2, "windowSeconds": 3 },
"conditions": {},
"entities": ["@scientists"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| --------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `amount` | number (min 2) | How many units of the underlying action form one burst. A value of 1 would change nothing and is rejected as a config error. |
| `windowSeconds` | number (min 1) | Length of the trailing window, in seconds. All `amount` units must land within one such span. |
amountRequired changes meaning
With a burst present, `amountRequired` counts **bursts**, not actions. “Kill 2 within 3 seconds, 5 times” is `amountRequired: 5` + `burst: { "amount": 2, "windowSeconds": 3 }`. The player performs 10 kills in total but only ever sees 2 and 5.
## One-shot challenges
[Section titled “One-shot challenges”](#one-shot-challenges)
Set `amountRequired: 1` for a single-cluster challenge, where the whole objective is one big burst:
* “Harvest 30 corn in 10 seconds” = `amountRequired: 1` + `burst: { "amount": 30, "windowSeconds": 10 }`
* “Deal 10,000 damage in a day” = `amountRequired: 1` + `burst: { "amount": 10000, "windowSeconds": 86400 }`
## How clustering works
[Section titled “How clustering works”](#how-clustering-works)
Each qualifying action becomes a timestamped **candidate** instead of counting immediately:
* Candidates older than the trailing window **expire silently**: they never count, and no credited progress is ever taken back. Progress only moves forward, one whole burst at a time.
* The moment the candidates inside the window reach `amount` units, one burst completes and `amountRequired` ticks by 1. Exactly `amount` units are consumed, oldest first.
* **Excess carries over.** The leftover units of a large action stay as a candidate with their original timestamp and seed the next cluster. A third rapid kill after a 2-kill burst starts the next pair. A single 1,200-damage hit against `amount: 500` fires 2 bursts (2 x 500) and carries 200 (1200 - 2 x 500 = 200).
* Candidates are tracked **per player, per objective**. They survive server restarts and die with the player’s progress: abandoning the contract or losing it to a rotation clears them.
The window here is the same **trailing (sliding) window** as [cooldown](/contracts/objectives/cooldown/): it always covers the last `windowSeconds` from the current moment, with no fixed reset tick. The only difference is the direction of the trigger. Cooldown counts progress inside the window and blocks when it exceeds `maxAmount`, while burst counts candidates inside the window and fires a burst when they reach `amount`.
Visualizing the window
Understand better visually? Check out this [blog post on sliding windows](https://smudge.ai/blog/ratelimit-algorithms#sliding-windows)
## Combining with cooldown
[Section titled “Combining with cooldown”](#combining-with-cooldown)
An objective can carry both a `burst` and a `cooldown`. Cooldown’s `maxAmount` shares `amountRequired`’s unit, so with a burst present it paces **completed bursts**: “kill 2 within 3 seconds, 5 times, at most 1 burst every 10 minutes” is a burst plus `cooldown: { "maxAmount": 1, "windowSeconds": 600 }`.
Caution
A burst completed while the cooldown is blocking is **wasted**: its candidates are consumed and the cooldown ignores the tick, exactly like any action that lands during a blocked window.
## What players see
[Section titled “What players see”](#what-players-see)
While candidates are pending, players get live feedback in both the contracts menu and the HUD: the sub-progress toward the next burst (e.g. `1/2`) and a countdown to when the oldest candidate expires. When nothing is pending, no burst UI shows. The burst UI is also hidden while a [cooldown](/contracts/objectives/cooldown/) on the same objective is blocking, since a burst completed during that time would be wasted. It reappears once the cooldown releases.
Windows shorter than the [`Hide Burst UI when window is below seconds`](/configuration/misc/) config (default 10) never show burst UI, avoiding flicker on very short windows: the progress bar simply advances one whole burst at a time. Set the config to `0` to always show the burst UI.
## Notes
[Section titled “Notes”](#notes)
* Burst is a **premium** feature. On the Lite edition, a contract whose objective carries a burst is [locked](/editions/): it never enters a rotation, and its definition is preserved untouched for when you upgrade.
* Burst is not a [condition](/contracts/conditions/). Conditions filter *when* an action counts based on the state of the world at that instant, while burst shapes *how* actions must be grouped over time.
# Card Swipe
> Swipe puzzle keycards in monument card readers.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Requires the player to swipe a keycard of specified access level(s) in a puzzle room card reader. Counts **1 per valid swipe**.
```json
{
"type": "CardSwipe",
"title": "Use green or blue keycards",
"description": "",
"amountRequired": 5,
"conditions": {},
"rewards": {},
"accessLevels": [1, 2]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| -------------- | ---------- | --------------------------------------------------- |
| `type` | string | Exactly 'CardSwipe' |
| `accessLevels` | integer\[] | Card-swipe access levels. Integers 1-3, all unique. |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many successful card swipes the player must perform. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## Notes
[Section titled “Notes”](#notes)
* Access levels map to the green (`1`), blue (`2`), and red (`3`) keycards.
# Cloning
> Take clones (cuttings) from specified plants.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Requires the player to take cuttings (clones) from the specified plants. Counts the **number of clones produced**.
```json
{
"type": "Cloning",
"title": "Take hemp cuttings",
"description": "",
"amountRequired": 20,
"conditions": {},
"rewards": {},
"items": ["clone.hemp"],
"genes": []
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Cloning'. |
| `items` | string\[] | Clone item shortNames to create (e.g. clone.corn, clone.hemp). Empty = any clone. |
| `genes` | object\[] | Optional gene filter: a list of { mode, composition } requirements where a plant counts if it matches at least one of these (OR). An empty or absent list (the default) counts any plant. composition is 1-6 letters from G/Y/H/W/X (Growth, Yield, Hardiness, Water, Empty) matched as a multiset (order ignored), and mode is 'Exact' (the full six-slot genome, short compositions padded with Empty) or 'AtLeast' (at least the listed count of each named type). |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many cuttings the player must take. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## Notes
[Section titled “Notes”](#notes)
* Progress increments by the **number of clones** a cutting produces. That count scales with the plant’s genetics: better Yield genes yield more clones per cutting, so a single cutting can advance the objective by more than one.
* See also [Harvest](/contracts/objectives/harvest/) for taking the yield and [Plant](/contracts/objectives/plant/) for planting seeds.
* Clones are always unskinned, so a `shortname:skin` entry warns in the editor and at load and never counts.
## Genes Property
[Section titled “Genes Property”](#genes-property)
The optional `genes` field restricts the objective to specific **gene compositions**. It is a list of requirements where any matching requirement counts (OR). An empty or absent list counts **any** gene composition (the default).
Each requirement is `{ mode: "Exact" | "AtLeast", composition: string }`:
* `composition` is 1 to 6 letters drawn from `G` (Growth), `Y` (Yield), `H` (Hardiness), `W` (Water), `X` (Empty). Order is ignored, only the count of each letter matters, so `GGYYYY`, `YGYGYG`, and `YYYYGG` are the same requirement.
* `mode` controls how the composition is matched:
* `Exact` (default): the plant’s full six-slot genome must equal the composition. A composition shorter than six letters pads the remaining slots with `X` (Empty), so `Exact "GGYY"` means exactly `GGYYXX`. (2 Growth, 2 Yield, 2 Empty in any order)
* `AtLeast`: the plant must have **at least** the listed count of each named gene type; the other slots can be anything. `AtLeast "YYYY"` counts any plant with 4 or more Yield genes.
```json
{
"type": "Cloning",
"title": "Clone elite corn",
"amountRequired": 20,
"items": ["clone.corn"],
"genes": [{ "mode": "AtLeast", "composition": "YYYYY" }]
}
```
A Cloning objective matches against the **parent plant’s** genes, the plant you take the cutting from.
# Cook
> Smelt, refine or cook specified items in ovens.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Requires the player to cook specified items in an oven.
Cook covers every oven conversion with one mechanic, because that is how the game itself models it: smelting ores in a furnace, refining crude oil in an oil refinery, and cooking raw food in a campfire or grill are all the same objective type.
```json
{
"type": "Cook",
"title": "Smelt metal ore",
"description": "",
"amountRequired": 500,
"conditions": {},
"rewards": {},
"items": ["metal.ore"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------- | --------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Cook' |
| `items` | string\[] | Input item shortNames to cook, lowercase dotted (e.g. metal.ore, sulfur.ore, crude.oil, bearmeat). Empty = any cookable item. |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many units of input items must finish cooking for the player. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## Who gets the credit
[Section titled “Who gets the credit”](#who-gets-the-credit)
Progress comes from **cooking**, but only on the units the **player deposited**. In other words, a unit counts the moment it **finishes converting in the oven**, and the credit goes to whoever **deposited that unit**. Depositing on its own earns nothing: it only stakes your claim on those units, and the claim pays out when they actually convert.
* Shared furnaces work the way you’d hope: teammates each get credit for the ore they deposited themselves, no matter who lit the furnace or who empties it.
* Progress keeps incrementing while the depositor is **offline**. Deposit items into a furnace, log off, and the objective advances (and can complete) as the ore converts.
* Items inserted by an **industrial conveyor** credit nobody, since no player deposited them. The plugin does not track which player placed the item that ended up in the conveyor.
## Surviving server & plugin restarts
[Section titled “Surviving server & plugin restarts”](#surviving-server--plugin-restarts)
The Cook objective persists in-memory cache data across server restarts and plugin reloads. When either happens, the plugin will save the state of oven conversion progress inside the `cache_data.json` file, and restore it when the server is back online or the plugin is reloaded.
This mechanic aims at satisfying long smelting quests that may be started before a server restart, and completed after the server is back online.
Server restart losses
When the server restarts, its ovens often restart themselves before the plugin is loaded. Items cooked during that time will not be credited, since the plugin cannot know which units were cooked while it was unloaded. Expect residual losses of progress in the event of a server restart while ovens are cooking quest items. In testing, this was often about 2-4 units only (per oven). But this greatly depends on how fast your server starts loading plugins after the world is initialized.
## Conditions apply at deposit time
[Section titled “Conditions apply at deposit time”](#conditions-apply-at-deposit-time)
Because of the lifecycle of all the events the Cook objective relies on and the uncertainty of player state, [conditions](/contracts/conditions/) are checked once against the depositor at the **instant the items are deposited**, not when they convert (cook). Depositing is the action you actually perform, so that is when your state is measured. By the time the items finish cooking you may be across the map or logged off.
* A [Monument](/contracts/conditions/monument/) condition effectively pins where the oven must be: the player has to be at the monument when depositing the items.
* A [TimeOfDay](/contracts/conditions/time-of-day/) condition means “load the oven at night”, not “the items must convert at night”.
Why not check conditions at conversion time?
When evaluating this caveat, it was determined that this was the most intuitive way to handle conditions for an “async” objective like Cook. Apart from the Time of Day condition, the majority of conditions wouldn’t have made sense (or even possible) if they were evaluated at the time of conversion.
## Notes
[Section titled “Notes”](#notes)
* Only **cookable conversions** count. Burning fuel does not: wood turning into charcoal is the furnace consuming fuel, not cooking a target item.
* The **cooking workbench** only counts through its furnace, not its [mixing table](/contracts/objectives/mix/). The name “Cook” follows the game’s own internal classification, and the mixing table is a separate mechanic the game calls “Mix”, covered by the [Mix](/contracts/objectives/mix/) objective. If you want prepared dishes like Hunter’s Pie, the Mix objective is the type you want.
* Counts the **number of input units cooked**: “smelt 500 metal ore” means 500 ore go in and finish converting, regardless of how many metal fragments come out.
# Cooldown
> Pace how fast progress adds up on an objective with a sliding-window limit.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
The optional `cooldown` field paces how fast an objective’s progress adds up: at most `maxAmount` units of progress count within any sliding window of `windowSeconds`. When the field is absent, progress adds up freely.
Cooldown is available on every **amount-based** objective. It is not part of the base objective shape: a future objective type that isn’t amount-based would not carry it (see the technical note on the [Objectives overview](/contracts/objectives/)).
```json
{
"type": "Kill",
"title": "Kill chickens, one every 5 minutes",
"description": "Only one kill counts every 5 minutes. Patience.",
"amountRequired": 5,
"cooldown": { "maxAmount": 1, "windowSeconds": 300, "hardCap": false },
"conditions": {},
"entities": ["chicken"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| --------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `maxAmount` | number (min 1) | How many units of progress can count within one window. Same unit as `amountRequired`, not a number of actions: one gather swing that yields 30 wood spends 30 units. |
| `windowSeconds` | number (min 1) | Length of the sliding window, in seconds. |
| `hardCap` | boolean | How an action that exceeds the remaining budget is handled. Defaults to `false` (soft). See [Soft vs hard cap](#soft-vs-hard-cap). |
## Why pace an objective?
[Section titled “Why pace an objective?”](#why-pace-an-objective)
* **Stop one-sitting farming.** A contract meant to last a rotation can otherwise be cheesed in minutes at a good farming spot.
* **Stretch engagement.** A paced objective brings players back to the contract several times over the rotation instead of once.
* **Soften snowballing.** High-value contracts completed in the first hour of a rotation flood the economy early, and pacing spreads the payout curve.
## When not to pace
[Section titled “When not to pace”](#when-not-to-pace)
* **It can frustrate more than it stretches.** A blocked objective means a player who wants to keep playing is told to stop. Some log off instead of coming back, especially on a short-session server.
* **It punishes the players you want.** Cooldown caps the fast, engaged players and does nothing to the casual ones who were never going to farm it in one sitting. The pace is set by your most active players but paid by everyone.
* **It shrinks the completion window.** Every pace pushes the minimum completion time up (see [Keep it completable](#keep-it-completable)). Stack a tight cooldown on a short rotation and the objective becomes impossible for anyone who joins late.
* **Over-pacing the whole set is worse than any single objective.** If most contracts are paced, players run out of things to do between windows and the server feels empty. Pace a few signature contracts and leave the rest free.
* **A condition may be the better tool.** If the goal is “only counts in the right situation” rather than “only counts this often”, a [condition](/contracts/conditions/) fits better and carries none of the waiting.
Rule of thumb
If you can’t name the farming behaviour you’re stopping, don’t use a cooldown.
## How the sliding window works
[Section titled “How the sliding window works”](#how-the-sliding-window-works)
The window **slides**: it always covers the trailing `windowSeconds` from the current moment. There is no fixed reset tick.
* The **budget** is `maxAmount` minus the progress already counted inside the trailing window. An action only counts while some budget remains.
* When the budget reaches zero the player is **blocked** until the oldest counted progress ages out of the window, which releases its amount back into the budget.
* At `maxAmount: 1` this reads as a classic cooldown: each counted increment locks the objective for the full window. At higher values it acts as a throughput cap (“up to 100 corn every 30 minutes”).
* The window is tracked **per player, per objective**. It survives server restarts and dies with the player’s progress: abandoning the contract or losing it to a rotation clears it.
Visualizing the window
Understand better visually? Check out this [blog post on sliding windows](https://smudge.ai/blog/ratelimit-algorithms#sliding-windows)
## Soft vs hard cap
[Section titled “Soft vs hard cap”](#soft-vs-hard-cap)
An action can be worth more than the remaining budget (a single harvest tick can yield more than one corn). `hardCap` decides what happens:
* **Soft (`false`, the default):** if *any* budget remains, the whole action counts in full. The window can overshoot `maxAmount` by up to one action’s worth. With 10 budget left, a harvest of 100 corn counts all 100.
* **Hard (`true`):** the action is clamped to the remaining budget and the excess is dropped. With 10 budget left, a harvest of 100 corn counts 10 and discards 90.
Soft feels fairer to players (no work is silently thrown away). Hard enforces the pace strictly. When in doubt, keep the default.
## What players see
[Section titled “What players see”](#what-players-see)
While a cooldown is limiting an objective, players get live feedback in both the contracts menu and the HUD:
* The **menu** shows a countdown to the next budget release, the amount that release returns, and the remaining window budget.
* E.g: “🕐 10:02 (+42) • 49/100” means that the player has a `49` amount budget left out of a maximum of `100`. In `10:02` minutes, the budget will increase by `42`.
* The **HUD** stays compact: it shows the remaining budget while some remains, and a countdown to the next release while blocked. A blocked objective is highlighted and its progress bar dimmed.
* An objective with no active window shows no cooldown UI at all.
Windows shorter than the [`Hide Cooldown UI when window is below seconds`](/configuration/misc/) config (default 10) never show cooldown UI, avoiding flicker on very short windows. The dimmed progress bar still communicates that the objective is blocked.
## Keep it completable
[Section titled “Keep it completable”](#keep-it-completable)
A cooldown puts a floor on how long the objective takes. The fastest possible completion is a full `maxAmount` burst immediately, then another as each window expires:
```text
minimum seconds = (ceil(amountRequired / maxAmount) - 1) * windowSeconds
```
Make sure that fits inside the rotation of **every category** the contract belongs to. For example, an objective asking to “harvest 300 corn, up to 100 every 30 minutes” has a minimum completion time of 60 minutes: two full harvests of 100, then a final harvest of 100 after the second window expires. Fine in a daily category, impossible in an hourly one.
```text
minimum seconds = (ceil(300 / 100) - 1) * 1800 = (3 - 1) * 1800 = 2 * 1800 = 3600 seconds = 60 minutes
```
You don’t have to do the math
The [web editor](https://editor.rustcontracts.com) raises a non-blocking warning when a cooldown makes an objective uncompletable within a category’s rotation.
## Examples
[Section titled “Examples”](#examples)
Two of the [default contracts](/getting-started/default-data/) use a cooldown:
**Free Range**: kill 5 chickens, at most 1 every 5 minutes. A classic cooldown.
```json
"cooldown": { "maxAmount": 1, "windowSeconds": 300, "hardCap": false }
```
**Corn Rationing**: harvest 300 corn, up to 100 every 30 minutes. A throughput cap with a soft limit, so a big harvest tick that crosses the 100 mark still counts in full.
```json
"cooldown": { "maxAmount": 100, "windowSeconds": 1800, "hardCap": false }
```
## Notes
[Section titled “Notes”](#notes)
* Cooldown is a **premium** feature. On the Lite edition, a contract whose objective carries a cooldown is [locked](/editions/): it never enters a rotation, and its definition is preserved untouched for when you upgrade.
* Cooldown is not a [condition](/contracts/conditions/). Conditions filter *when* an action counts based on the state of the world at that instant, while cooldown limits *how fast* counted progress adds up over time.
* The inverse also exists: [burst](/contracts/objectives/burst/) ignores actions that come too *slow* instead of too fast, and both can be combined on one objective.
# Craft
> Create specified items.
Requires the player to craft specified items. Counts the **number of items crafted**.
```json
{
"type": "Craft",
"title": "Craft bandages",
"description": "",
"amountRequired": 20,
"conditions": {},
"rewards": {},
"items": ["bandage"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Craft' |
| `items` | string\[] | Rust item shortNames, lowercase dotted (e.g. rifle.ak, rifle.bolt, wood, stones, metal.fragments, scrap, hqm, lowgradefuel). Add :skin with the skin's workshop ID to match only that skinned item (paper:3731371060). A bare short name matches any skin, and :0 matches only the unskinned item. |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many of the target items the player must craft. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## Notes
[Section titled “Notes”](#notes)
* Progress increments when a craft **finishes**, by the **crafted stack amount**. Crafting a stack of 10 counts as 10.
# Crate Hack
> Hack hackable locked crates.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Requires the player to hack hackable locked crates. Counts **1 per crate hacked**.
```json
{
"type": "CrateHack",
"title": "Hack locked crates",
"description": "",
"amountRequired": 3,
"conditions": {},
"rewards": {}
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------ | ------ | ------------------- |
| `type` | string | Exactly 'CrateHack' |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many locked crates the player must hack. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## Notes
[Section titled “Notes”](#notes)
* Progress increments by **one** each time a Hackable Locked Crate finishes its hack countdown.
* Credit goes to the player who **started** the hack.
# Damage
> Inflict damage on specified entities.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Requires the player to deal damage to specified entities. `amountRequired` is measured in **raw HP of damage dealt**, not a number of hits.
```json
{
"type": "Damage",
"title": "Wound the scientists",
"description": "",
"amountRequired": 1000,
"conditions": {},
"rewards": {},
"entities": ["scientistnpc_heavy"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ---------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Damage' |
| `entities` | string\[] | Rust entity short prefab names, lowercase (e.g. bear, wolf, boar, stag for animals; scientistnpc\_heavy, scientistnpc\_full\_any, murderer for NPCs; ridablehorse, minicopter.entity for mounts). Use the living entity's name, not its .corpse variant. Custom NPC display names also match, like the names set with BetterNpc or NpcSpawn (e.g. Boss Guard). Names are not case sensitive. |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many points of damage the player must deal to the target entities. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## Notes
[Section titled “Notes”](#notes)
Caution
It’s highly recommended to add at least one target entity to the `entities` list. An entity can also be a non-living object that can take damage, such as a door, corpse, a vehicle, a dropped backpack, … Leaving it empty effectively **any** damageable Rust *thing* (living or not) gets tracked, which can be extremely CPU intensive since every single hit on any entity will get tracked.
* Progress increments by the **total damage** of each hit on a listed entity. An `amountRequired` of `500` means 500 HP of cumulative damage.
* **Self-damage is ignored**: you can’t progress by hurting yourself.
* Matched by the victim’s prefab short name, or by **display name** for custom NPCs from plugins like BetterNpc or NpcSpawn. Real player names never match. Matching is not case sensitive.
* A kill also deals damage, so this overlaps with [Kill](/contracts/objectives/kill/) on the same entity by design.
* Find the correct entity names to use in your contracts with the [Getting item & entity names](/guides/getting-item-and-entity-names/) guide.
# Dig
> Dig up metal detector spots, optionally only ones that turn up specified items.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Requires the player to dig up the spots a metal detector finds, optionally only ones that turn up specified items. Counts **1 per finished dig**.
```json
{
"type": "Dig",
"title": "Dig up high quality metal",
"description": "",
"amountRequired": 3,
"conditions": {},
"rewards": {},
"items": ["metal.refined"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Dig' |
| `items` | string\[] | Rust item shortNames, lowercase dotted (e.g. rifle.ak, rifle.bolt, wood, stones, metal.fragments, scrap, hqm, lowgradefuel). Add :skin with the skin's workshop ID to match only that skinned item (paper:3731371060). A bare short name matches any skin, and :0 matches only the unskinned item. |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many digs the player must finish. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## Notes
[Section titled “Notes”](#notes)
* Progress goes up by **one per finished dig**, no matter how many items the dig turns up.
* The `items` list decides which digs count:
* **Empty**: any finished dig counts, even one that turns up nothing.
* **Non-empty**: only digs that turn up a listed item count.
* Only the player who lands the last hit on the flag gets credit. Players who helped dig it get nothing.
# Objective display
> What players see of an objective's fields, conditions and rewards, and how authors influence it.
The **objective display** is the plugin’s generated, player-facing rendering of what an objective asks for and what it pays. This was introduced in version 1.5.0 after the growing amount of conditions made it inconvenient to have to describe them all in the objective’s description. It is generated from the config itself, so it is always accurate and up-to-date.
It has two parts, both in the contract details panel of the menu (the HUD is untouched):
* **Condition strip**: a row of small icons on the objective card, one per condition. At most 10 icons show, then a `+N` chip.
* **Objective details popup**: click anywhere on the objective card to open it. It shows the objective’s own requirements (targets, items, amounts, and other type-specific fields), the full conditions tree, with `ALL OF` / `ANY OF` / `NOT` groups indented the way they nest, and the objective’s rewards with their amounts.
## What players see
[Section titled “What players see”](#what-players-see)
* **Items** show their in-game icon and display name (e.g. `rifle.ak` renders as the Assault Rifle icon and name).
* **Entities** show a curated display name plus a category icon (NPC, animal, mount or world object). An entity outside the curated set falls back to its raw shortname and a generic icon. Unfortunately (afaik), Rust does not expose display names for entities, so entity display names are currently hard-coded into the plugin. This may be improved in a future update.
* **Presets are never unpacked.** A field containing `@rifles` renders as a single chip named `rifles`. See [Presets](/presets/): preset names are player-visible text, so name them well. A preset can also carry its own chip icon and color, see the [preset fields](/presets/#fields).
* **Values render in words**: “Between 21:00 and 06:00”, “From at least 50m”, “Wearing any of”, and so on. Every generated label is a language message you can override or translate like any other plugin message. See [Localization](/localization/).
## How authors influence it
[Section titled “How authors influence it”](#how-authors-influence-it)
* **The description stays yours.** The generated display supplements it. Use the description for flavor and intent, not field-by-field repetition.
* **`hideDetails`** (per objective, default `false`): set it to `true` and that objective shows no condition strip, its card is not clickable, and no popup exists. Players get only your title and description. Use it when discovery is the point (riddle objectives) or when the generated rendering would spoil a surprise.
* **Preset names are copy.** `@rifles` reads great, `@preset_v2_final` does not.
## Locked and secret objectives
[Section titled “Locked and secret objectives”](#locked-and-secret-objectives)
* In a **Sequential** contract, locked objectives show the strip and popup dimmed like the rest of the card, so players can plan ahead.
* In a **Progressive** contract, locked objectives are secret: “???” stays absolute. No strip, no popup, regardless of `hideDetails`.
# Fishing
> Catch specified fish using specific bait.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Requires the player to catch specified fish, optionally using specific bait. Counts **1 per fish caught**.
```json
{
"type": "Fishing",
"title": "Catch fish for dinner",
"description": "",
"amountRequired": 10,
"conditions": {},
"rewards": {},
"fish": ["fish.anchovy", "fish.herring"],
"bait": ["worm"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------ | --------- | ------------------------------------------------------------------------------------ |
| `type` | string | Exactly 'Fishing' |
| `fish` | string\[] | Rust item shortNames (e.g. fish.anchovy, fish.herring, fish.sardine). |
| `bait` | string\[] | Items usable as fishing bait (e.g. worm, grub, fish.minnows, bearmeat, chicken.raw). |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many of the target fish the player must catch. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## Notes
[Section titled “Notes”](#notes)
* Progress increments by **one each time** a listed fish is caught.
* The `bait` list gates *how* the fish is caught:
* **Non-empty**: the bait currently loaded in the rod must be one of the listed baits, or the catch doesn’t count.
* **Empty**: any bait qualifies.
* A caught fish is always unskinned, so a skinned entry in `fish` warns and never counts.
# Gamble
> Wager scrap at Rust's gambling games, counting bets won, lost, played, or the scrap they moved.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Requires the player to wager scrap at one of Rust’s four gambling games: the wheel of fortune, a slot machine, a card table dealing Texas Hold’em, or blackjack.
```json
{
"type": "Gamble",
"title": "Bandit's luck",
"description": "",
"amountRequired": 1,
"conditions": {},
"rewards": {},
"measurement": "Wins",
"games": ["Wheel"],
"wheelMultipliers": [20]
}
```
Experimental
The gamble objective has arguably one of the most (if not *the* most) complex logic of any objective and testing it is even more difficult, since it requires actual luck! While I’ve tested it extensively and all default gambling contracts are verified completable, there may be edge cases that I haven’t thought of or simply haven’t stumbled across yet.
If you have issues with it, please report them on the [Discord server](https://discord.rustcontracts.com) with reproduction steps or a video of the issue. Simply saying “it doesn’t work” is not informative and won’t help me fix it, since I can’t reproduce the issue without more information.
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Gamble' |
| `measurement` | enum: Wins \| Losses \| Plays \| ScrapWon \| ScrapLost \| ScrapWagered | What to count on each settled bet. 'Wins', 'Losses' and 'Plays' count bets: a win is a bet that ended with positive net scrap, a loss is one that ended negative, and 'Plays' counts every settled bet whichever way it went. 'ScrapWon', 'ScrapLost' and 'ScrapWagered' sum scrap instead, always as positive amounts: net scrap gained, net scrap given up, and total staked. Defaults to 'Plays'. |
| `games` | enum\[]: Wheel \| Slots \| Poker \| Blackjack | Gambling games whose bets count: Wheel (wheel of fortune, played at its betting), Slots (a slot machine), Poker (a card table dealing Texas Hold'em), Blackjack (a blackjack machine). Empty = any game. |
| `minScrapPerBet` | integer \| null (min 1) | Optional per-bet floor on the scrap amount the measurement counts: the net won for 'Wins' and 'ScrapWon', the net lost for 'Losses' and 'ScrapLost', the stake for 'Plays' and 'ScrapWagered'. A settled bet below the floor does not count at all, so with 'Wins' this means 'win at least this much on a single bet'. |
| `wheelMultipliers` | enum\[]: 1 \| 3 \| 5 \| 10 \| 20 | Only count wheel bets placed on these color multipliers: 1 (yellow), 3 (green), 5 (blue), 10 (purple), 20 (red). Empty = any color. Requires 'Wheel' in 'games'. |
| `pokerHands` | enum\[]: RoyalFlush \| StraightFlush \| FourOfAKind \| FullHouse \| Flush \| Straight \| ThreeOfAKind \| TwoPair \| Pair \| HighCard | Only count poker rounds won with one of these hand categories, best first: RoyalFlush, StraightFlush, FourOfAKind, FullHouse, Flush, Straight, ThreeOfAKind, TwoPair, Pair, HighCard. The game evaluates hands only at a showdown, so a pot won because everyone folded matches no category. RoyalFlush and StraightFlush are separate categories, list both to accept either. Empty = any hand. Requires 'Poker' in 'games'. |
| `blackjackResults` | enum\[]: Bust \| Loss \| Standoff \| Win \| BlackjackWin | Only count blackjack rounds that produced one of these results: Bust, Loss, Standoff, Win, BlackjackWin (a natural 21). A round matches when any of its hands ended on a listed result, so a split hand still counts once. Empty = any result. Requires 'Blackjack' in 'games'. |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How much the player must accumulate, in the unit set by 'measurement': settled bets for 'Wins', 'Losses' and 'Plays', scrap for the scrap measurements. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## What counts as one bet
[Section titled “What counts as one bet”](#what-counts-as-one-bet)
Progress is measured in **settled bets**: one wager resolving into a win or a loss. What that is depends on the game.
| Game | One settled bet is |
| ----------- | -------------------------------------------------------------------------------------- |
| `Wheel` | one color slot resolving when the wheel stops |
| `Slots` | one spin of a slot machine |
| `Poker` | one poker round |
| `Blackjack` | one blackjack round, with its split hand and insurance side bet netted together as one |
A bet exists only at the instant it settles:
* A wheel spin settles **one bet per color the player backed**. Covering all five colors on one spin is one win, four losses and five plays.
* Scrap dropped into a betting terminal and pulled back out before the wheel spins never happened. Not a play, not a wager.
* Folding for free is not a bet. A round the player put nothing into does not count.
## What counts as a win or a loss
[Section titled “What counts as a win or a loss”](#what-counts-as-a-win-or-a-loss)
Scrap is always counted **net excluding the player’s own stake**, and the sign of that net decides the outcome: positive is a win, negative is a loss, and exactly zero is neither.
The wheel returns the stake along with the winnings, so a **500 scrap bet on a 1x color pays back 1000 and counts as 500 won**, not 1000. A 500 scrap bet on a losing color counts as 500 lost.
That one rule covers all four games:
* A slot machine payout smaller than the spin cost is a **loss**, even though the machine paid something.
* A blackjack standoff and a chopped poker pot are **neither** a win nor a loss, because the player ended level. They still count for `Plays` and `ScrapWagered`.
* A poker side-pot winner who only made their money back is not a win either.
## Measurements
[Section titled “Measurements”](#measurements)
`measurement` picks what one unit of `amountRequired` means. The first three count bets, the last three sum scrap.
| Measurement | One unit is |
| -------------- | -------------------------------------------------- |
| `Wins` | one settled bet that ended with positive net scrap |
| `Losses` | one settled bet that ended with negative net scrap |
| `Plays` | one settled bet, win or lose |
| `ScrapWon` | one scrap of net winnings |
| `ScrapLost` | one scrap of net losses |
| `ScrapWagered` | one scrap staked, whatever came back |
## Notes
[Section titled “Notes”](#notes)
* **Scrap only.** A modded slot machine that pays out something other than scrap is ignored.
* Progress reaches the player who **staked the scrap**, and it keeps adding up while they are **offline**: the plugin remembers who put what into a wheel terminal, so two players sharing one terminal each get their own bets. A condition that needs a live player fails for an absent one, as everywhere else.
# Gather
> Collect items from dispensers, like trees and ore nodes.
Requires the player to gather resources from the world: wood from trees, ore from nodes, meat from animal corpses. Counts the **amount gathered**.
```json
{
"type": "Gather",
"title": "Gather wood",
"description": "",
"amountRequired": 5000,
"conditions": {},
"rewards": {},
"items": ["wood"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Gather' |
| `items` | string\[] | Rust item shortNames, lowercase dotted (e.g. rifle.ak, rifle.bolt, wood, stones, metal.fragments, scrap, hqm, lowgradefuel). Add :skin with the skin's workshop ID to match only that skinned item (paper:3731371060). A bare short name matches any skin, and :0 matches only the unskinned item. |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many of the target items the player must gather. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## Notes
[Section titled “Notes”](#notes)
* Fires when a **resource dispenser** is harvested (a tree, ore node, or animal corpse). Progress increments by the **gathered item amount**.
* A [Weapon condition](/contracts/conditions/weapon/) on a Gather objective is approximated by the player’s **currently held item**, because Rust doesn’t expose which tool did the gathering.
* Gather counts what the node itself hands out, so a bonus item another plugin adds to the inventory never counts.
# Harvest
> Harvest plants from planters or the world.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Requires the player to harvest plants, whether they grew them or found them growing wild. Counts the **yield amount** taken.
```json
{
"type": "Harvest",
"title": "Harvest pumpkins",
"description": "",
"amountRequired": 50,
"conditions": {},
"rewards": {},
"items": ["pumpkin"],
"source": "Planted",
"genes": []
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| -------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Harvest'. |
| `items` | string\[] | Harvested produce item shortNames (e.g. corn, pumpkin, black.berry). Empty = any plant. |
| `source` | enum: Any \| World \| Planted | Whether the harvested plant must be wild (World), player-grown (Planted), or either (Any). |
| `genes` | object\[] | Optional gene filter: a list of { mode, composition } requirements where a plant counts if it matches at least one of these (OR). An empty or absent list (the default) counts any plant. composition is 1-6 letters from G/Y/H/W/X (Growth, Yield, Hardiness, Water, Empty) matched as a multiset (order ignored), and mode is 'Exact' (the full six-slot genome, short compositions padded with Empty) or 'AtLeast' (at least the listed count of each named type). |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many produce items the player must harvest. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## Notes
[Section titled “Notes”](#notes)
* Progress increments by the **harvested item amount** (the plant’s yield).
* `source` filters by the plant’s **origin**: *who it belongs to*, not where it sits:
* `World`: the plant grew wild (no owner).
* `Planted`: a player grew it. A player-grown plant counts as `Planted` wherever it was grown, in a planter or in the ground.
* `Any`: either.
* In some cases, Harvest overlaps with [Loot](/contracts/objectives/loot/): picking a wild pumpkin is both a Harvest and a Loot. This is because both involve the same Rust event triggered (`OnCollectiblePickup`).
* See also [Cloning](/contracts/objectives/cloning/) for taking cuttings and [Plant](/contracts/objectives/plant/) for planting seeds.
## Genes Property
[Section titled “Genes Property”](#genes-property)
The optional `genes` field restricts the objective to specific **gene compositions**. It is a list of requirements where any matching requirement counts (OR). An empty or absent list counts **any** gene composition (the default).
Each requirement is `{ mode: "Exact" | "AtLeast", composition: string }`:
* `composition` is 1 to 6 letters drawn from `G` (Growth), `Y` (Yield), `H` (Hardiness), `W` (Water), `X` (Empty). Order is ignored, only the count of each letter matters, so `GGYYYY`, `YGYGYG`, and `YYYYGG` are the same requirement.
* `mode` controls how the composition is matched:
* `Exact` (default): the plant’s full six-slot genome must equal the composition. A composition shorter than six letters pads the remaining slots with `X` (Empty), so `Exact "GGYY"` means exactly `GGYYXX`. (2 Growth, 2 Yield, 2 Empty in any order)
* `AtLeast`: the plant must have **at least** the listed count of each named gene type; the other slots can be anything. `AtLeast "YYYY"` counts any plant with 4 or more Yield genes.
```json
{
"type": "Harvest",
"title": "Harvest top-tier corn",
"amountRequired": 50,
"items": ["corn"],
"source": "Planted",
"genes": [{ "mode": "Exact", "composition": "GGYYYY" }]
}
```
A genes filter only applies to **grown plants**. Instant world pickups (wild mushrooms, hemp or berries) carry no genome, so an objective with a genes filter never counts them. A Harvest objective with `source` set to `World` and a `genes` filter is effectively never completable: wild plants grow with random genes, so they almost never match a specific filter, and instant world pickups carry no genome at all. The plugin corrects such objectives to `source: "Any"` automatically.
# Heal
> Restore an amount of health.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Requires the player to restore health using specified items, such as food or medical items. `amountRequired` is measured in **raw HP restored**.
```json
{
"type": "Heal",
"title": "Patch up your team",
"description": "",
"amountRequired": 500,
"conditions": {},
"rewards": {},
"items": ["syringe.medical", "largemedkit"],
"target": "Others"
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| -------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Heal'. |
| `items` | string\[] | Rust item shortNames, lowercase dotted (e.g. rifle.ak, rifle.bolt, wood, stones, metal.fragments, scrap, hqm, lowgradefuel). Add :skin with the skin's workshop ID to match only that skinned item (paper:3731371060). A bare short name matches any skin, and :0 matches only the unskinned item. |
| `target` | enum: Any \| Self \| Others | Whether the heal must target self, others, or either. |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many points of health the player must restore. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## Notes
[Section titled “Notes”](#notes)
* Progress increments by the **HP actually restored**, clamped to the target’s **missing** health. Over-healing past full HP does not count, and healing a full-health target counts nothing.
* `target` controls whose healing qualifies:
* `Self`: only healing yourself.
* `Others`: only healing other players.
* `Any`: either.
Known issue
When using a medical syringe, HP is gradually restored over a short time. However, the full potential HP gain counts immediately, not as the HP is restored. If something interrupts the healing (such as taking damage), the progress won’t reflect the actual HP restored.
# Kill
> Eliminate specified entities.
Requires the player to kill specified entities. Counts **1 per kill**.
```json
{
"type": "Kill",
"title": "Hunt bears",
"description": "",
"amountRequired": 5,
"conditions": {},
"rewards": {},
"entities": ["animal_bear"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ---------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Kill'. |
| `entities` | string\[] | Rust entity short prefab names, lowercase (e.g. bear, wolf, boar, stag for animals; scientistnpc\_heavy, scientistnpc\_full\_any, murderer for NPCs; ridablehorse, minicopter.entity for mounts). Use the living entity's name, not its .corpse variant. Custom NPC display names also match, like the names set with BetterNpc or NpcSpawn (e.g. Boss Guard). Names are not case sensitive. |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many of the target entities the player must kill. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## Notes
[Section titled “Notes”](#notes)
* Progress increments by **1 each time** a listed entity dies.
* Contrary to what you might expect, an *entity* is not necessarily something living. In Rust, an entity is any object in the game world, living or not. The kill objective works for any entity that can receive damage, including animals, players, NPCs, barrels, roadsigns and even deployables like furnaces and boxes. This means you can use the kill objective to create objectives like “Destroy 5 barrels”.
* Matching is by the victim’s **prefab short name**. Custom NPCs from plugins like BetterNpc or NpcSpawn can also be matched by their **display name** (e.g. `Boss Guard`). Real player names never match, so players can’t rename themselves to farm a contract. Matching is not case sensitive.
* Find the correct entity names to use in your contracts with the [Getting item & entity names](/guides/getting-item-and-entity-names/) guide.
* When several players damaged the victim, **each attacker** can receive kill credit. Apply a [MinDamageRatio](/contracts/conditions/min-damage-ratio/) condition to require a minimum damage contribution for credit.
* Killing an entity also counts toward a [Damage](/contracts/objectives/damage/) objective for the same entity, and the overlap is intentional.
# Loot
> Obtain items from the world or world containers.
Requires the player to loot specified items from the world or non-player-owned containers. Counts the **number of items** obtained.
```json
{
"type": "Loot",
"title": "Loot scrap from crates",
"description": "",
"amountRequired": 100,
"conditions": {},
"rewards": {},
"items": ["scrap"],
"containers": []
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Loot' |
| `items` | string\[] | Rust item shortNames, lowercase dotted (e.g. rifle.ak, rifle.bolt, wood, stones, metal.fragments, scrap, hqm, lowgradefuel). Add :skin with the skin's workshop ID to match only that skinned item (paper:3731371060). A bare short name matches any skin, and :0 matches only the unskinned item. |
| `containers` | string\[] | Rust entity short prefab names of the containers the items must come out of, or @preset refs (e.g. supply\_drop for the airdrop, crate\_normal for a military crate, @Hackable Crates, @Barrels, scientist2.corpse for a scientist body). Empty means any source, the ground included. Use the body's name, not the living NPC's. Names are not case sensitive. |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many of the target items the player must loot. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## Notes
[Section titled “Notes”](#notes)
Caution
It’s highly recommended to add at least one target item to the `items` list. Leaving it empty means **any** item gets tracked, which can be **extremely memory-intensive** since every single picked up item will get tracked and subject to an in-memory taint.
* This objective type is not recommended for use. Read the “Not recommended” section below for details.
* Progress follows the items as they move (including stack splits), crediting the player who received them.
* Loot overlaps intentionally with [Harvest](/contracts/objectives/harvest/): picking a wild pumpkin is both a Loot and a Harvest.
### Containers
[Section titled “Containers”](#containers)
Use `containers` to specify the list of containers that items must have come out of. Leave it empty for the objective to count an item no matter where it came from: a crate, a barrel, a body, or the ground. It takes entity names or `@preset` references, the same as the [Kill](/contracts/objectives/kill/) objective’s `entities` list. Case doesn’t matter.
```json
{
"type": "Loot",
"title": "Loot scrap from the airdrop",
"description": "",
"amountRequired": 100,
"conditions": {},
"rewards": {},
"items": ["scrap"],
"containers": ["supply_drop"]
}
```
Leave `items` empty instead and you get any item from specific containers: `"items": []` with `"containers": ["@Hackable Crates"]` is “loot any 10 items out of a hackable crate”.
Four presets ship with the plugin so you don’t have to list the prefab names yourself:
| Preset | Covers |
| ------------------ | ---------------------------------------------------------------- |
| `@Normal Crates` | The brown wooden crates, including the ones on train wagons |
| `@Military Crates` | The green military crates |
| `@Hackable Crates` | All five hackable crate variants (oil rig, ghost ship, the rest) |
| `@Barrels` | Road barrels, oil barrels and diesel barrels |
Finding a container’s name
Don’t know what a container’s name is? Read the [Getting Item and Entity names](/guides/getting-item-and-entity-names/) guide to find their entity name. (TL;DR; for this, I recommend using `entity.debug_lookat` console command while looking at the container in-game.)
#### Notes on Containers
[Section titled “Notes on Containers”](#notes-on-containers)
* **Ground pickups stop counting.** An item lying on the ground has no container to match against, so it never counts for that objective. Same for berries, mushrooms and other collectibles.
* **Barrels count what they spill.** A barrel dumps its loot on the ground as it breaks, so there is nothing to open. The plugin remembers which barrel each item fell out of until you walk over and pick it up, so `@Barrels` counts the way you’d expect.
* **Bodies go by the corpse name**, not the NPC’s name. Every scientist type leaves the same `scientist2.corpse` and a scarecrow leaves `murderer_corpse`. `@Scientists` names the living NPC, so it does nothing here (unless you add `scientist2.corpse` to it, which the default preset doesn’t have).
* Player boxes and player bodies still never count, with or without a containers list. This is to prevent exploits. See the tainting system below.
#### Known Limitations
[Section titled “Known Limitations”](#known-limitations)
* Scrap Yield tea gives you bonus scrap that was never inside the barrel, so a barrel filter skips it.
* A body that despawns leaves an `item_drop_backpack` bag behind. Items taken out of the bag match the bag’s name, not the corpse’s.
* Some crates share a short name across monuments: `crate_elite` spawns in radtown and in underwater labs and a containers list can’t tell them apart. Pair it with a [Monument](/contracts/conditions/monument/) condition if you need to.
## Not recommended - use [Turn In](/contracts/objectives/turn-in/) instead
[Section titled “Not recommended - use Turn In instead”](#not-recommended---use-turn-in-instead)
The `containers` list is the one thing only Loot can do, since Turn In has no idea where an item came from. For everything else, Turn In is the safer pick. The Loot objective sounds simple on paper, but it is amongst one of the most technically complex objective of the plugin and the most likely to have exploits or edge cases. The complexity comes from the mechanisms required to prevent a player from removing and re-adding the same item to a container to farm progress. There are also a bunch of ways for a player to get items (dragging a stack to their inventory, splitting a stack into their inventory, picking up an item from the world, etc.). All of these actions fire on different Rust events, include different payloads and required me to implement a whole transfer and tainting system just to track item movements.
### The tainting system
[Section titled “The tainting system”](#the-tainting-system)
In the simplest terms, the system works like this: when an item is moved from a player container to a non-player-owned container or the world, the plugin “taints” (remembers) that item in memory. Whenever any player picks up a tainted item, the plugin skips progressing the loot objective and “untaints” the item to free memory.
The plugin already does a lot to untaint items as soon as possible, but it is not perfect:
* Picking up a tainted item from the world or a world container untaints it immediately. The plugin doesn’t need to track items not in world containers, because they can’t be used for progress anyway.
* When stacking two tainted items together, the resulting stack stays tainted, but the source stack (which no longer exists) is untainted.
* Tainted items that despawn from the world are untainted.
* However, if the tainted item is in a world container (e.g. a chest) and that container despawns, the taint will remain in memory indefinitely. This is arguably the biggest source of lingering tainted items.
### How many tainted items is “a lot”?
[Section titled “How many tainted items is “a lot”?”](#how-many-tainted-items-is-a-lot)
Each tainted item is a single network ID (8 bytes) in a hashset, so figure roughly **30 bytes each** once you account for overhead (of the hashset data structure). The count only matters relative to RAM, and that bar is high:
| Tainted items | Approx. RAM | What it means |
| ---------------------------- | ---------------------- | ---------------------------------------------------- |
| up to \~100k | up to \~3 MB | Normal. Nothing to do. |
| \~100k to \~1M | \~3 to 30 MB | Fine, but a sign of long uptime or heavy item churn. |
| millions, climbing each save | tens to hundreds of MB | Worth clearing. |
The number resets on every server/plugin restart, so a normal wipe cycle keeps it well inside the first row. Read it as a growth signal, not a memory threat: if it’s climbing by hundreds of thousands every save, restart or run `contracts.debug.clear_tainted_items`. The absolute number on its own is rarely a reason to act.
On my PVE server, which restarts every 24 hours, the count usually sits at around 250 tainted items at the end of the day.
### Known limitations that will not be addressed
[Section titled “Known limitations that will not be addressed”](#known-limitations-that-will-not-be-addressed)
The loot objective has some known limitations that are accepted and will not be fixed, either because the fix would involve worse memory/performance tradeoffs or because it would open up potential over-count exploits. The loot objective aims to prevent over-counting, even if that means under-counting in some edge cases.
* This objective will only progress for items that are moved from the world or a world-owned container to a player inventory. This means looting an item from a player body or a player-owned container will not progress the objective. This is intentional to prevent over-counting progress exploits by moving items between player-owned containers.
* If a player drags a stack of their items onto a world container’s stack of the same item (that is untainted), then the entire stack becomes tainted, because any item that leaves the player inventory must become tainted. This entire stack will be ineligible for loot progress. Fixing this would require tracking the stack’s network ID *AND* the tainted amount, which would be a significant increase in memory footprint compared to only tracking the network ID.
* E.g: Say a scientist’s body has an untainted stack of 10 scrap. If you grab the 10 scraps, then it counts. However, if you drag 1 scrap from your inventory onto that stack of 10, it becomes a tainted stack of 11. If you then loot from that stack, it does not count, even if you only take 1 scrap.
### Use Turn In instead of Loot
[Section titled “Use Turn In instead of Loot”](#use-turn-in-instead-of-loot)
For all these reasons, it’s recommended to use the [Turn In](/contracts/objectives/turn-in/) objective instead of Loot whenever possible. Because the Turn In objective takes away the item from the player, it avoids all the complexities of tracking items in the world and inventory, and is much more straightforward to implement and manage.
# Mix
> Mix specified items at a mixing table or cooking workbench.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Requires the player to mix specified items at a mixing table or cooking workbench.
```json
{
"type": "Mix",
"title": "Mix pure ore tea",
"description": "",
"amountRequired": 3,
"conditions": {},
"rewards": {},
"items": ["oretea.pure"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Mix' |
| `items` | string\[] | Produced item shortNames to mix, lowercase dotted (e.g. oretea.pure, healingtea, pie.hunters). These are the items the table makes, not the ingredients it consumes. Empty = any mixed item. |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many produced items must come out of the player's mixes. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## Notes
[Section titled “Notes”](#notes)
* A **server restart** loses an in-progress mix, because the game itself never restarts a mix that was running when the server went down. A plugin reload does not: the mix keeps running and still credits its starter.
* Mixing-table output is never **crafted**, so a [Craft](/contracts/objectives/craft/) objective will not count it, and none of these items can be hand-crafted.
* The editor only warns that an item shortname exists, never that the item can actually be produced at a mixing table. A valid but unmixable item simply never progresses.
* Mixed items are always unskinned, so a `shortname:skin` entry warns in the editor and at load and never counts.
# Plant
> Plant specified seeds.
Requires the player to plant the specified seeds. Counts **1 per seed planted**.
```json
{
"type": "Plant",
"title": "Plant hemp seeds",
"description": "",
"amountRequired": 10,
"conditions": {},
"rewards": {},
"items": ["seed.hemp"],
"genes": []
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Plant'. |
| `items` | string\[] | Seed item shortNames to plant (e.g. seed.corn, seed.hemp). Empty = any seed. |
| `genes` | object\[] | Optional gene filter: a list of { mode, composition } requirements where a plant counts if it matches at least one of these (OR). An empty or absent list (the default) counts any plant. composition is 1-6 letters from G/Y/H/W/X (Growth, Yield, Hardiness, Water, Empty) matched as a multiset (order ignored), and mode is 'Exact' (the full six-slot genome, short compositions padded with Empty) or 'AtLeast' (at least the listed count of each named type). |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many seeds the player must plant. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## Notes
[Section titled “Notes”](#notes)
* Progress increments by **one** each time a listed seed is planted.
* An **empty** `items` list counts **any** seed.
* See also [Harvest](/contracts/objectives/harvest/) for taking the yield and [Cloning](/contracts/objectives/cloning/) for taking cuttings.
## Genes Property
[Section titled “Genes Property”](#genes-property)
The optional `genes` field restricts the objective to specific **gene compositions**. It is a list of requirements where any matching requirement counts (OR). An empty or absent list counts **any** gene composition (the default).
Each requirement is `{ mode: "Exact" | "AtLeast", composition: string }`:
* `composition` is 1 to 6 letters drawn from `G` (Growth), `Y` (Yield), `H` (Hardiness), `W` (Water), `X` (Empty). Order is ignored, only the count of each letter matters, so `GGYYYY`, `YGYGYG`, and `YYYYGG` are the same requirement.
* `mode` controls how the composition is matched:
* `Exact` (default): the plant’s full six-slot genome must equal the composition. A composition shorter than six letters pads the remaining slots with `X` (Empty), so `Exact "GGYY"` means exactly `GGYYXX`. (2 Growth, 2 Yield, 2 Empty in any order)
* `AtLeast`: the plant must have **at least** the listed count of each named gene type; the other slots can be anything. `AtLeast "YYYY"` counts any plant with 4 or more Yield genes.
```json
{
"type": "Plant",
"title": "Plant high-yield corn",
"amountRequired": 10,
"items": ["seed.corn"],
"genes": [{ "mode": "AtLeast", "composition": "YYYY" }]
}
```
A Plant objective matches against the **seed’s own chosen genes**, not the plant that later grows from it. Initial seeds with “Unknown” genes will never match a genes filter.
# Playtime
> Spend time connected, alive and awake, measured in minutes of play.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Requires the player to play an amount of minutes on the server. A minute counts while the player is connected, alive and awake.
```json
{
"type": "Playtime",
"title": "Play 1 hour",
"description": "Time counts while you are connected, alive and awake. Sleeping does not.",
"amountRequired": 60,
"conditions": {},
"rewards": {}
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------ | ------ | ------------------ |
| `type` | string | Exactly 'Playtime' |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many minutes of play the player must play on the server. When a burst is present, this counts completed bursts instead of minutes. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## What counts as playing
[Section titled “What counts as playing”](#what-counts-as-playing)
The plugin samples each tracked player about once a second and credits one progress point per full minute of qualifying time.
* **Connected, alive and awake.** All three at once. The respawn screen and sleeping do not count.
* **Idle time counts.** A player standing still in their base is still playing. There is no AFK detection today, but integrating the [AFKAPI](https://umod.org/plugins/afkapi) plugin is a possible future addition.
* **Only whole minutes are credited.** Partial minutes carry over in memory between samples.
## Conditions filter moment to moment
[Section titled “Conditions filter moment to moment”](#conditions-filter-moment-to-moment)
Conditions are checked at each \~1 second sample, and only qualifying seconds accumulate toward the next minute. “Spend an hour at Launch Site” is Playtime plus a [Monument condition](/contracts/conditions/monument/): seconds outside the monument simply do not count, however long the player stays online.
## Pacing
[Section titled “Pacing”](#pacing)
Each credited minute is one action, so [Cooldown](/contracts/objectives/cooldown/) and [Burst](/contracts/objectives/burst/) can be combined to defined play schedules. This means you can use Playtime to express “play an hour a day, 7 days” or “play 2 hours in one sitting, once a week”. This is kind of an unexpected side-effect of the objective that made the planned [Connect objective](https://app.notion.com/p/rustcontracts/Connect-Objective-3a11c1211e658079a3bfd272891b3bbd?v=3541c1211e6580ec9001000cefa0fd0b\&source=copy_link) unnecessary and scoped out of the [roadmap](https://roadmap.rustcontracts.com).
```json
{
"type": "Playtime",
"title": "Play an hour a day, 7 days",
"description": "A full hour of play within a day counts once. Do it seven times.",
"amountRequired": 7,
"burst": { "amount": 60, "windowSeconds": 86400 },
"cooldown": { "maxAmount": 1, "windowSeconds": 86400 }
}
```
The burst turns 60 minutes inside a 24 hour window into one completed burst, the cooldown caps it at one burst per 24 hours, and `amountRequired: 7` asks for seven of them. A plain cooldown alone also works: `maxAmount: 30, windowSeconds: 86400` caps credit at 30 minutes a day.
## Sampling efficiency
Some objectives cannot be driven by a game event, because the game never announces “this player moved 12 meters” or “this player walked into Launch Site”. Progress for those has to be measured by checking on players directly, about once a second.
Contracts does that in a single shared pass for every objective that needs it, not one pass per objective:
* **Nothing runs unless something needs it.** The pass only exists while the current rotation actually contains an objective that depends on it. Rotate those out and it stops completely.
* **More objectives do not mean more work per player.** A dozen Travel and Visit objectives spread across your categories are all served by the same pass, which reads each player once and hands that one reading to whichever objectives care.
* **The expensive checks are narrow.** Reading a position is cheap; the heavier work, like monument bounds and condition checks, only happens for players who have one of these objectives active and unfinished.
At this time, the [Travel](/contracts/objectives/travel/), [Visit](/contracts/objectives/visit/) and [Playtime](/contracts/objectives/playtime/) objectives use this sampling pass.
## Notes
[Section titled “Notes”](#notes)
* Progress ticks about once a minute while the player qualifies, so the UI advances in whole-minute steps.
* `amountRequired` is in **minutes**. In the editor you type it as a natural-language duration such as `1h 30m` or `90`. A bare number means minutes.
# Recycle
> Recycle specified items, counting either what goes into the recycler or what comes out.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Requires the player to recycle items, counting either the items fed into the recycler or the items it returns.
```json
{
"type": "Recycle",
"title": "Scrap run",
"description": "",
"amountRequired": 5,
"conditions": {},
"rewards": {},
"direction": "Input",
"items": ["pipes"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ----------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Recycle' |
| `direction` | enum: Input \| Output | Which side of the recycler counts. 'Input' counts the units fed in, and the items list holds what to feed in. 'Output' counts the units that come out, and the items list holds what must come out. |
| `items` | string\[] | Item shortNames on the side named by 'direction', lowercase dotted. With 'Input', these are items to recycle (e.g. pipes, techparts, roadsigns). With 'Output', these are items the recycler must return, scrap included (e.g. metal.fragments, scrap). Empty = any item on that side. |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many items must pass through the recycler on the side named by 'direction'. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## Who gets the credit
[Section titled “Who gets the credit”](#who-gets-the-credit)
Progress goes to the player who **loaded the items into the recycler**, not to whoever switched it on or emptied the output tray. That holds for both directions: with `"Output"`, the items produced from your pipes are yours even if somebody else is standing there when they appear.
* Shared recyclers work the way you’d hope. Everyone gets credit for what they put in themselves.
* Progress keeps adding up while the depositor is **offline**. Load a recycler, log off, and the objective advances as it runs.
* [Conditions](/contracts/conditions/) are checked once, against the depositor at the instant of deposit, exactly like [Cook](/contracts/objectives/cook/). A [Monument](/contracts/conditions/monument/) condition therefore pins where the recycler must be, and a [TimeOfDay](/contracts/conditions/time-of-day/) condition means “load the recycler at night” (not “the items must come out at night”).
## Surviving server & plugin restarts
[Section titled “Surviving server & plugin restarts”](#surviving-server--plugin-restarts)
The Recycle objective persists in-memory cache data across server restarts and plugin reloads. When either happens, the plugin will save the state of recycler progress inside the `cache_data.json` file, and restore it when the server is back online or the plugin is reloaded.
This mechanic aims at preserving who gets the credit for items deposited into a recycler between server/plugin restarts.
## Notes
[Section titled “Notes”](#notes)
* Every recycler counts, at a monument, in a safe zone or in a monument. There is no filter for which one, only for the items.
* The editor only warns that an item shortname exists, never that a recycler accepts it or can produce it. A valid but unrecyclable item simply never progresses.
* When the output tray fills up, the recycler drops the rest at its feet and stops. Those dropped items still count as output.
* Recycler output is always unskinned, so a skinned entry with `Output` warns and never counts.
# Repair
> Repair items at a bench, or restore HP to structures, deployables and vehicles with the hammer.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Requires the player to repair things. The `target` field decides what gets repaired and what a unit of progress is.
```json
{
"type": "Repair",
"title": "Service your kit",
"description": "",
"amountRequired": 5,
"conditions": {},
"rewards": {},
"target": "Item",
"targets": ["jackhammer"]
}
```
```json
{
"type": "Repair",
"title": "Patch up the base",
"description": "",
"amountRequired": 1000,
"conditions": {},
"rewards": {},
"target": "Entity",
"targets": ["wall", "foundation", "floor"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| --------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Repair' |
| `target` | enum: Item \| Entity | What gets repaired and what a unit is. 'Item' counts repair actions, one per repair bench repair or workbench refill, and the targets list holds item shortNames. 'Entity' counts HP restored by hammering structures, deployables or vehicles, and the targets list holds entity short prefab names. Repairing a vehicle module at a car lift counts as 'Entity' HP restored to the module, not as an item repair. |
| `targets` | string\[] | What must be repaired, named by 'target'. With 'Item', item shortNames, lowercase dotted (e.g. jackhammer, rifle.ak, metal.plate.torso). With 'Entity', entity short prefab names (e.g. wall, foundation, furnace, autoturret\_deployed, minicopter.entity). Empty = anything repairable. |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How much the player must repair, in the unit set by 'target': repair actions with 'Item', HP restored with 'Entity'. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## What counts as a unit of progress
[Section titled “What counts as a unit of progress”](#what-counts-as-a-unit-of-progress)
* With `"Item"`, a unit is **one repair action**: a successful repair at a repair bench, or a workbench refill. The `targets` list holds item shortnames, so “repair 5 weapons” is written as `amountRequired: 5` with the weapons listed.
* With `"Entity"`, a unit is **one HP restored**: hammer swings on building blocks, deployables, vehicles, boats and trains, plus vehicle module repairs at a car lift. The `targets` list holds entity short prefab names, so “restore 3000 HP of structures” is `amountRequired: 3000`.
An empty `targets` list means anything repairable counts. The two targets are not combinable in one objective, so use two objectives if you want both.
## What counts as a repair
[Section titled “What counts as a repair”](#what-counts-as-a-repair)
* **Repair bench** repairs count as `"Item"` actions, one per repaired item.
* **Workbench refills** count as `"Item"` actions too. Items like the jackhammer cannot be repaired at a bench, but placing them into a workbench and refilling them is the game’s only repair path for them, and it counts.
* **Hammer swings** count as `"Entity"` HP. Entity amounts move in chunks rather than one point at a time.
* **Car lift module repairs** count as `"Entity"` HP restored to the module, even though the lift technically repairs the module item. A “repair vehicles” contract catches both the hammer and the lift this way. Only non-module items count toward `"Item"` progress.
A repair that restores nothing counts nothing. Swinging at a full-health wall, hitting the recently-damaged window, or lacking resources all progress zero. Hammering an under-construction siege weapon is construction, not repair, and never counts.
## No anti-farm guard
[Section titled “No anti-farm guard”](#no-anti-farm-guard)
If the game allowed the repair, it counts, whoever the target belongs to. A player can damage their own stuff and repair it to farm a repair objective. Re-repairing your own walls is paced by the repair’s resource cost. For that reason, it is **highly recommended** that you put a [cooldown](/contracts/objectives/cooldown/) on the objective.
## Notes
[Section titled “Notes”](#notes)
* The editor suggests curated structure and deployable names for the `"Entity"` target, but any valid short prefab name works. The editor only warns that a name exists, never that it is repairable. A valid but unrepairable entity simply never progresses.
# Travel
> Cover distance across the world by any means, measured in meters of 3D displacement.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Requires the player to cover distance, measured in meters of world displacement. Any means of getting around counts: walking, sprinting, swimming, riding a horse, driving, flying, falling.
```json
{
"type": "Travel",
"title": "Saddle up",
"description": "Distance only counts while you are in the saddle.",
"amountRequired": 2500,
"conditions": {
"mount_condition": {
"type": "PlayerMount",
"mounts": ["ridablehorse"]
}
},
"rewards": {}
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------ | ------ | ---------------- |
| `type` | string | Exactly 'Travel' |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many meters of world distance the player must cover. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## What counts
[Section titled “What counts”](#what-counts)
The plugin samples each tracked player’s world position about once a second and credits the straight-line distance between consecutive samples.
* **Distance is 3D.** Vertical movement counts: climbing a ladder, ballooning up, diving down, falling.
* **Any means counts.** On foot, mounted, swimming, gliding, and even being carried: a player standing on a moving cargo ship deck still gains travel distance, because they really are moving through the world.
* **Only whole meters are credited.** Leftover fractions carry over in memory between samples.
## What never counts
[Section titled “What never counts”](#what-never-counts)
* **Teleports.** A jump between samples too fast to be legitimate movement is dropped entirely. The plugin hardcodes a threshold of 150 meters per second. Moving faster than that is considered a teleport.
* **Death.** Dying and respawning never credits the distance to the respawn point, however close the bag.
## Filtering by method
[Section titled “Filtering by method”](#filtering-by-method)
Pair Travel with the [PlayerMount condition](/contracts/conditions/player-mount/) for by-vehicle contracts: “travel 2500m on horseback” is Travel plus PlayerMount `ridablehorse`, “cross 4000m by boat” is Travel plus PlayerMount with boat entities.
For unmounted methods, pair with the [PlayerMovement condition](/contracts/conditions/player-movement/) instead: “travel 500m on foot” is Travel plus PlayerMovement `OnFoot`, “swim 200m” is Travel plus PlayerMovement `Swimming`. Conditions are checked at each sample, so mounting or dismounting mid-segment misattributes at most about a second of travel.
## Pacing
[Section titled “Pacing”](#pacing)
Each sampled segment is one action worth its length in meters, so [Cooldown](/contracts/objectives/cooldown/) and [Burst](/contracts/objectives/burst/) compose naturally: “sprint 50m within 10s, 5 times” is a Travel objective with a burst of 50 in a 10 second window and `amountRequired: 5`.
## Sampling efficiency
Some objectives cannot be driven by a game event, because the game never announces “this player moved 12 meters” or “this player walked into Launch Site”. Progress for those has to be measured by checking on players directly, about once a second.
Contracts does that in a single shared pass for every objective that needs it, not one pass per objective:
* **Nothing runs unless something needs it.** The pass only exists while the current rotation actually contains an objective that depends on it. Rotate those out and it stops completely.
* **More objectives do not mean more work per player.** A dozen Travel and Visit objectives spread across your categories are all served by the same pass, which reads each player once and hands that one reading to whichever objectives care.
* **The expensive checks are narrow.** Reading a position is cheap; the heavier work, like monument bounds and condition checks, only happens for players who have one of these objectives active and unfinished.
At this time, the [Travel](/contracts/objectives/travel/), [Visit](/contracts/objectives/visit/) and [Playtime](/contracts/objectives/playtime/) objectives use this sampling pass.
## Notes
[Section titled “Notes”](#notes)
* Progress ticks about once a second while the player is moving, and completion can land mid-journey between UI refreshes.
# Turn In
> Submit items to the plugin.
Requires the player to turn in specified items to the plugin. Turned-in items are **removed from the player’s inventory**. Counts the **number of items** submitted.
```json
{
"type": "TurnIn",
"title": "Turn in low grade fuel",
"description": "",
"amountRequired": 500,
"conditions": {},
"rewards": {},
"items": ["lowgradefuel"]
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'TurnIn'. |
| `items` | string\[] | Rust item shortNames, lowercase dotted (e.g. rifle.ak, rifle.bolt, wood, stones, metal.fragments, scrap, hqm, lowgradefuel). Add :skin with the skin's workshop ID to match only that skinned item (paper:3731371060). A bare short name matches any skin, and :0 matches only the unskinned item. |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many of the target items the player must turn in. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## Notes
[Section titled “Notes”](#notes)
* Unlike most objectives, turning in is a **player-initiated action** in the menu, not a passive event. The player can turn in only while the objective is active and incomplete and they hold at least one matching item.
* Submitting **consumes** the items: they leave the player’s inventory.
* Turning in happens inside the Contracts menu, using the “Turn In” button on the objective’s UI entry.
# Visit
> Arrive at monuments. Each arrival from fully outside counts once.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Requires the player to arrive at one of the listed monuments.
```json
{
"type": "Visit",
"title": "Visit Launch Site",
"description": "Get inside the grounds. Mind the radiation.",
"amountRequired": 1,
"monuments": ["launch_site_1"],
"conditions": {},
"rewards": {}
}
```
Caution
This objective requires the free [Monument Finder](https://umod.org/plugins/monument-finder) plugin on the server, even with an empty monument list. Contracts using it are **dependency-locked** while Monument Finder is not installed: they are never drawn into rotations, can’t be accepted, and stay invisible to players. Their data is untouched, so installing (or loading) Monument Finder makes them available again without a restart. Unloading it mid-rotation expires affected active contracts, except already claimed ones.
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ----------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Visit' |
| `monuments` | string\[] | The monuments that count as arrival targets. Each entry is matched case-insensitively against a monument's short name (such as 'launch\_site\_1' or 'oilrig\_1') or its Monument Finder alias (such as 'TrainStation'). An empty list means arriving at any monument counts. |
Shared fields (10)
| Field | Type | Description |
| ------------------------------------------------------------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Short, human-readable name. Must not be empty. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A blurb of 1 to 3 sentences. May be empty if the title is self-explanatory. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `amountRequired` | integer (min 1) | How many arrivals are required. When requiring more than one, pair the objective with a cooldown, otherwise players can simply enter/leave/enter the same monument to farm arrivals. |
| [`cooldown`](/contracts/objectives/cooldown/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional pacing limit on how fast progress adds up: at most maxAmount units of progress count in any sliding window of windowSeconds. Absent means progress adds up freely. While the window's budget is used up, further progress is ignored and the player sees a countdown to the next available moment. |
| [`burst`](/contracts/objectives/burst/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | object \| null | Optional clustering requirement: actions only count when they arrive clustered, at least amount units within one trailing window of windowSeconds forming one burst. When present, amountRequired counts completed bursts instead of raw actions. Actions that age out of the window without completing a burst never count. Absent means no clustering. |
| [`conditions`](/contracts/conditions/) | map | Optional conditions that restrict when progress counts. Progress is recorded only when the objective's main requirement and all of these conditions are met. Use an empty map for none. |
| [`rewards`](/contracts/rewards/) [⭐ Premium](/editions/ "Premium feature (see Editions)") | map | Rewards claimed when this objective completes, instead of at the end of the contract. Each entry is a full reward, the same shape the contract's own rewards use. Use an empty map for none. |
| `hideDetails` | boolean | When true, hides this objective's in-game condition strip and details popup, so the description is the only explanation players get. Title, description and progress still show. Absent means false. |
## What counts as an arrival
[Section titled “What counts as an arrival”](#what-counts-as-an-arrival)
The plugin checks each tracked player’s position about once a second against the combined bounds of every listed monument, treated as one area.
* **An arrival is going from fully outside to inside a monument.** Overlapping or nested bounds never re-credit: a player moving from a train tunnel up into Launch Site, with both listed, has never left the union, so nothing new counts.
* **Standing inside already counts.** Tracking starts fresh when the objective activates, when the player connects, and when the plugin reloads. A player inside the bounds at that moment credits one arrival at the first check. This is deliberate: “I’m already here” should count.
* **Any way in counts.** Walking, flying, respawning at a bag inside the bounds, teleporting in. There is no movement requirement, only the outside-to-inside transition (or presence at tracking start).
* **Leaving is required between arrivals.** To credit again, the player must exit the whole union first, then come back in. The same monument is repeatable, and arrivals are never counted per distinct monument.
## Pair multiple visits with a Cooldown
[Section titled “Pair multiple visits with a Cooldown”](#pair-multiple-visits-with-a-cooldown)
When `amountRequired` is above 1, stepping out of the bounds and straight back in credits another arrival. That is by design, so that you have freedom over pacing, but it makes unpaced multi-visit objectives trivial to farm at a monument’s edge. Add a [Cooldown](/contracts/objectives/cooldown/) such as 1 per 300 seconds so each visit has to stand on its own. The editor shows a non-blocking warning for multi-visit objectives without one.
## Touring several monuments
[Section titled “Touring several monuments”](#touring-several-monuments)
Arrivals are counted against the union of the list, never per distinct monument. To send players on a tour, author a **Sequential** contract whose objectives are single-monument Visits: “Visit Launch Site”, then “Visit The Dome”, then “Visit the Airfield”. Each step reads as clear guidance and completes in order.
## Sampling efficiency
Some objectives cannot be driven by a game event, because the game never announces “this player moved 12 meters” or “this player walked into Launch Site”. Progress for those has to be measured by checking on players directly, about once a second.
Contracts does that in a single shared pass for every objective that needs it, not one pass per objective:
* **Nothing runs unless something needs it.** The pass only exists while the current rotation actually contains an objective that depends on it. Rotate those out and it stops completely.
* **More objectives do not mean more work per player.** A dozen Travel and Visit objectives spread across your categories are all served by the same pass, which reads each player once and hands that one reading to whichever objectives care.
* **The expensive checks are narrow.** Reading a position is cheap; the heavier work, like monument bounds and condition checks, only happens for players who have one of these objectives active and unfinished.
At this time, the [Travel](/contracts/objectives/travel/), [Visit](/contracts/objectives/visit/) and [Playtime](/contracts/objectives/playtime/) objectives use this sampling pass.
## Notes
[Section titled “Notes”](#notes)
* Detection lags reality by up to one check (\~1 second). A player clipping a monument corner between checks may not credit.
* Respawning or teleporting into the bounds counts as an arrival, and a reconnect or plugin reload while standing inside credits again. Cooldown paces these like any other re-entry.
# Rewards
> What rewards are, the fields every reward shares, and the reward types.
A **reward** is what a contract pays out on completion. A contract needs at least one. The `type` field selects which reward it is and which fields it carries. Each per-type page in this section lists its complete field set, shared and type-specific alike.
Every reward shares `title`, `description`, an `eligiblePermissions` list that gates who can receive it, and a `hidden` flag that keeps it out of the menu.
## When rewards are granted
[Section titled “When rewards are granted”](#when-rewards-are-granted)
A completed contract’s reward is granted when the player **claims** it. Claiming is deliberate: the player must open the contract’s menu and click the “Claim” button. Otherwise, the reward will be granted automatically when the contract expires (if the player has completed it).
## Hidden rewards
[Section titled “Hidden rewards”](#hidden-rewards)
Set `hidden` to `true` to keep a reward out of the menu. Players don’t see it on the contract card, in the contract’s reward list or on the objective, but it still pays out when they claim. It also still counts for claiming: a contract, objective or prestige tier whose rewards are all hidden still shows its Claim button, just with no rewards listed.
This is handy for a side effect that isn’t really a reward, like adding the player to a chat group, next to the rewards you do want players to see:
```json
"rewards": {
"scrap": {
"type": "Item",
"title": "",
"description": "",
"eligiblePermissions": [],
"hidden": false,
"item": "scrap",
"quantity": 250,
"skinId": null,
"itemName": null,
"itemNameTranslations": null
},
"hunter_chat_group": {
"type": "Command",
"title": "",
"description": "",
"eligiblePermissions": [],
"hidden": true,
"commands": ["oxide.usergroup add {playerId} hunters"],
"reverseCommands": ["oxide.usergroup remove {playerId} hunters"],
"icon": null,
"iconColor": null,
"iconRaw": false
}
}
```
## Error handling and reward reversion
[Section titled “Error handling and reward reversion”](#error-handling-and-reward-reversion)
During the reward distribution process, if an error occurs, the plugin will attempt to revert any rewards that were succesfully granted before the error.
If the plugin is able to revert all granted rewards, the player will be notified and the contract will remain completed (unclaimed) so that they can try claiming again later.
However, if the plugin is unable to revert any granted rewards or has granted unrevertible rewards (i.e. a Command reward), the player will be notified, but the contract will be marked as claimed and the reward will be considered lost. In this case, the player should contact an admin for assistance.
[Command](/contracts/rewards/command/) rewards and [Custom](/contracts/rewards/custom/) rewards using a Command definition run console commands, and a fired command cannot be taken back on its own. Both can declare [reverse commands](/contracts/rewards/command/#reverse-commands) that tell the plugin how to undo the commands, which makes them revertible like any other reward. Without one they stay unrevertible: once granted, if an error occurs later during the reward distribution process, the plugin will not attempt to revert rewards and the contract will be marked as claimed. A Custom reward using an Item definition gives items, so it is taken back like an Item reward. For this reason, the plugin grants natively revertible rewards first, then command-based rewards with reverse commands, and unrevertible ones last.
## Not the rewards you’re looking for? Need something custom?
[Section titled “Not the rewards you’re looking for? Need something custom?”](#not-the-rewards-youre-looking-for-need-something-custom)
If the rewards below don’t fit your needs, think again! Most people overlook the the [Command Reward](/contracts/rewards/command/) type! If you need to reward your players with a feature backed by another plugin, that plugin most likely has a command that you would usually run yourself to give your players that feature (XP, battlepass points, leaderboard score, etc.). The Command Reward allows you to run that command for your players automatically when they complete a contract. You can even use placeholders in the command to customize it for each player!
When the same command shows up on many contracts, name it once as a [custom reward definition](/custom-rewards/) and reference it with a [Custom Reward](/contracts/rewards/custom/). You write the command in one place, and every contract that references it stays in sync. The same goes for an item you pay on many contracts, like a server currency.
Almost everything is a [Command Reward](/contracts/rewards/command/)!
When you really look at it, most rewards natively supported by Contracts can be replaced with a Command Reward:
* The [Item Reward](/contracts/rewards/item/) is close to a Command Reward with `commands` set to: `inventory.giveto {playerId} rifle.ak 1 `. The game’s own give commands cannot change the display name of an item, but an Item Reward or an Item [custom reward definition](/custom-rewards/#item-definitions) can. Contracts also exposes its own equivalent of the `inventory.giveto` command you can use anywhere (even outside of Contracts) and it supports custom display names: `contracts.give_item [amount] [skinId] [name]`.
* The [Economics Reward](/contracts/rewards/economics/) is a Command Reward with `commands` set to: `economics.deposit {playerId} 100`
* The [Server Rewards Reward](/contracts/rewards/server-rewards/) is a Command Reward with `commands` set to: `rp add {playerId} 2`
## Reward types
[Section titled “Reward types”](#reward-types)
[Command](/contracts/rewards/command/)Run console commands when the reward is claimed.
[Custom](/contracts/rewards/custom/)Pay a custom reward definition when the reward is claimed: run its command or give its item.
[Economics](/contracts/rewards/economics/)Grant in-game currency via the Economics plugin.
[Item](/contracts/rewards/item/)Grant a quantity of a Rust item.
[Contract Score](/contracts/rewards/score/)Grant Contract Score (CS), the plugin's own non-spendable points.
[Server Rewards](/contracts/rewards/server-rewards/)Grant RP via the ServerRewards plugin.
[Skill Tree Points](/contracts/rewards/skill-tree-points/)Grant Skill Tree skill points via the Skill Tree plugin.
[Skill Tree XP](/contracts/rewards/skill-tree-xp/)Grant Skill Tree XP via the Skill Tree plugin.
# Command
> Run console commands when the reward is claimed.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Runs one or more server console commands when the reward is claimed. This is the escape hatch for granting anything the other reward types can’t: kits, ranks, custom plugin actions. Technically, all rewards can be achieved with the Command reward!
```json
{
"type": "Command",
"title": "Supply signal",
"description": "",
"eligiblePermissions": ["contracts.vip"],
"hidden": false,
"commands": ["inventory.giveto {playerId} supply.signal 1"],
"reverseCommands": [],
"icon": null,
"iconColor": null,
"iconRaw": false
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ----------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Command' |
| `commands` | string\[] | Console commands to run when the reward is claimed, top to bottom. Every command supports runtime placeholders such as {playerId} and {playerName}. |
| `reverseCommands` | string\[] | Console commands that take back what the commands gave, with the same placeholders. They run in the order written, and only when claiming fails halfway and the rewards already given roll back. Empty when the reward cannot be taken back. |
| `icon` | string \| null | Direct image URL for this reward's icon in the menu. Null (or omitted) keeps the default star icon, or the per-type Command override from the plugin config when one is set. |
| `iconColor` | string \| null | Color for the icon, as hex (#rrggbb or #rrggbbaa), rgb() or rgba(). Null (or omitted) inherits the per-type or default color. Invalid colors warn at plugin load and render white. |
| `iconRaw` | boolean | Set true when the icon is a full-color image (a logo, custom art) so it renders in its own colors. When false the image renders as a solid color, which is right for monochrome icons. |
Shared fields (6)
| Field | Type | Description |
| ------------------------------------------------------------------------------ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Display name. May be empty to use the reward type's default name (recommended for most rewards). |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A short blurb of 1 to 3 sentences. May be empty. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `eligiblePermissions` [⭐ Premium](/editions/ "Premium feature (see Editions)") | string\[] | Permission strings that gate this reward, using any-of logic: a non-empty list grants the reward to any player holding at least one of the listed permissions. An empty array means everyone is eligible. |
| `hidden` | boolean | If true, players don't see this reward in the menu. It still pays out on claim and still makes the contract, objective or prestige tier claimable. |
## Custom icon
[Section titled “Custom icon”](#custom-icon)
Command rewards can grant anything, so a single default icon rarely fits. The `icon`, `iconColor` and `iconRaw` fields let each reward carry its own image: a reward granting a heli signal and one granting a kit can look like what they give.
Each field falls back on its own. A null `icon` keeps the default image, a null `iconColor` keeps the default color, and the default here means the per-type `Command` entry from the plugin config’s [UI icons](/configuration/ui/) when one is set, or the built-in star otherwise.
Set `iconRaw` to `true` when the image is full-color (a logo, item art, a plugin’s own icon). Without it the client renders the image as a single-color silhouette, which only looks right for monochrome icons. A raw icon with an `iconColor` still gets colored, and ineligible or locked rewards still dim.
## Placeholders
[Section titled “Placeholders”](#placeholders)
Every command in `commands` and `reverseCommands` can include these placeholders, substituted at run time:
| Placeholder | Replaced with |
| --------------- | -------------------------------------------- |
| `{playerId}` | The player’s Steam ID |
| `{playerName}` | The player’s display name |
| `{qPlayerName}` | The player’s display name, wrapped in quotes |
| `{playerX}` | The player’s X position |
| `{playerY}` | The player’s Y position |
| `{playerZ}` | The player’s Z position |
## Reverse commands
[Section titled “Reverse commands”](#reverse-commands)
A fired console command normally cannot be taken back. If a Command reward is granted and a later reward in the same claim fails, the plugin cannot undo it, so the contract is [marked as claimed and the failed rewards are lost](/contracts/rewards/#error-handling-and-reward-reversion).
The `reverseCommands` list fixes that. It holds the commands that take back what `commands` gave, with the same placeholders. With it set, the plugin can safely roll back the reward when a claim fails halfway, and the player can try claiming again later:
```json
{
"commands": ["givexp {playerId} 1000 false"],
"reverseCommands": ["takexp {playerId} 1000"]
}
```
The two lists don’t need the same number of commands. The reverse commands run in the order you write them, so put them in the order that undoes things correctly. They only run when a claim fails halfway, never on a normal claim. Leave the list empty (`[]`) when the reward cannot be undone.
## Notes
[Section titled “Notes”](#notes)
* The command runs on the **server console**, with the same authority as a server admin.
* Use `{qPlayerName}` when a name with spaces must be passed as a single argument. Otherwise, some command might misinterpret the player’s name as multiple arguments.
* Reusing the same command across many contracts? Define it once as a [custom reward definition](/custom-rewards/) and reference it with a [Custom reward](/contracts/rewards/custom/) instead.
## Examples
[Section titled “Examples”](#examples)
### Swap a chat group
[Section titled “Swap a chat group”](#swap-a-chat-group)
Move the player from the `tier2` group to `tier3`, for a chat tag that changes with each [prestige tier](/prestige/). If the claim fails, the reverse commands put the player back in `tier2`:
```json
{
"commands": [
"oxide.usergroup remove {playerId} tier2",
"oxide.usergroup add {playerId} tier3"
],
"reverseCommands": [
"oxide.usergroup remove {playerId} tier3",
"oxide.usergroup add {playerId} tier2"
]
}
```
Note
In this example, Oxide groups are added. Contracts does not handle permission cleanup (e.g. at wipe time). That’s still your responsibility. Use other plugins like Timed Permissions to manage this.
### Heli Signals
[Section titled “Heli Signals”](#heli-signals)
Give an easy heli signal to the completing player:
```text
hsgive easy {playerId} 1
```
### Ganja
[Section titled “Ganja”](#ganja)
Give weed to the completing player:
```text
ganja.give weed low_quality 10 {playerId}
```
### XPerience
[Section titled “XPerience”](#xperience)
Give 50 XP to the completing player:
```text
xpgive {playerId} 50
```
### Rust Items
[Section titled “Rust Items”](#rust-items)
> This is what the [Item Reward](/contracts/rewards/item/) does.
Give 500 scrap to the completing player:
```text
inventory.giveto {playerId} scrap 500
```
### Economics Money
[Section titled “Economics Money”](#economics-money)
> This is what the [Economics Reward](/contracts/rewards/economics/) does.
Give $1000 to the completing player:
```text
deposit {playerId} 1000
```
### ServerRewards RP
[Section titled “ServerRewards RP”](#serverrewards-rp)
> This is what the [ServerRewards Reward](/contracts/rewards/server-rewards/) does.
Give 2 RP to the completing player:
```text
rp add {playerId} 2
```
### Skill Tree XP
[Section titled “Skill Tree XP”](#skill-tree-xp)
> This is what the [Skill Tree XP Reward](/contracts/rewards/skill-tree-xp/) does.
Give 1000 XP to the completing player:
```text
givexp {playerId} 1000 false
```
# Custom
> Pay a custom reward definition when the reward is claimed: run its command or give its item.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
References a [custom reward definition](/custom-rewards/) by key and pays it when the reward is claimed. A Command definition runs its console commands, an Item definition gives its item. A definition names a command or an item once, and every contract that uses it stays in sync: edit the definition and every reward that references it is edited too, including contracts you wrote months ago.
```json
{
"type": "Custom",
"title": "",
"description": "",
"eligiblePermissions": [],
"hidden": false,
"reward": "HeliSignal",
"amount": 1,
"icon": null,
"iconColor": null,
"iconRaw": false
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ----------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Custom' |
| `reward` | string | Key of the custom reward definition to pay, from the CustomRewards file. |
| `amount` | number \| null | Amount substituted into the definition's {amount} placeholder, or the number of items for an Item definition. Required and greater than 0 for an Item definition (a whole number) or when the command contains {amount}. May be a decimal for a command. |
| `icon` | string \| null | Direct image URL for this reward's icon in the menu. Null (or omitted) falls back to the definition's icon. With no icon on either, an Item definition shows its item, and a Command definition shows the per-type Custom override from the plugin config, then the default lightning icon. |
| `iconColor` | string \| null | Color for the icon, as hex (#rrggbb or #rrggbbaa), rgb() or rgba(). Null (or omitted) inherits the definition, per-type or default color. Invalid colors warn at plugin load and render white. |
| `iconRaw` | boolean | Set true when the icon is a full-color image (a logo, custom art) so it renders in its own colors. When false the image renders as a solid color, which is right for monochrome icons. |
Shared fields (6)
| Field | Type | Description |
| ------------------------------------------------------------------------------ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Display name. May be empty to use the reward type's default name (recommended for most rewards). |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A short blurb of 1 to 3 sentences. May be empty. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `eligiblePermissions` [⭐ Premium](/editions/ "Premium feature (see Editions)") | string\[] | Permission strings that gate this reward, using any-of logic: a non-empty list grants the reward to any player holding at least one of the listed permissions. An empty array means everyone is eligible. |
| `hidden` | boolean | If true, players don't see this reward in the menu. It still pays out on claim and still makes the contract, objective or prestige tier claimable. |
## Notes
[Section titled “Notes”](#notes)
* `reward` is the definition’s key from the [custom rewards data file](/custom-rewards/), matched without caring about upper or lower case.
* As of 1.14.0, Skill Tree XP has its own [reward type](/contracts/rewards/skill-tree-xp/) and no longer needs a custom reward definition.
* Whether `amount` is required comes from the definition. An Item definition always needs a whole number of 1 or more, the number of items to give. A Command definition with `{amount}` in any of its commands needs an amount greater than 0 (decimals allowed). When it does not, set `amount` to `null`.
* The `amount` you set here is the base. The server’s [reward multiplier tiers](/configuration/rewards/#multipliers) can pay some players more than that. For an Item definition the result is rounded to a whole number and never drops below 1.
* A decimal `amount` on a reward using an Item definition is rounded at load, with a warning. The editor flags it before you export.
* When the definition does not exist (deleted, or a typo), the editor shows a warning but still lets you export. On the server, the plugin logs a warning at load and treats the reward as ineligible for every player. The contract stays playable, only that reward is never granted.
* When no title is set, the reward shows the definition’s display name. A null `icon` falls back to the definition’s icon. With no icon on either, an Item definition shows its item with its skin, and a Command definition shows the per-type `Custom` entry from the plugin config’s [UI icons](/configuration/ui/), then the built-in icon.
* An Item definition pays like an [Item reward](/contracts/rewards/item/), full inventory and rollback included. See [Item definitions](/custom-rewards/#item-definitions).
# Economics
> Grant in-game currency via the Economics plugin.
Grants an amount of Economics currency to the player when the reward is claimed.
```json
{
"type": "Economics",
"title": "",
"description": "",
"eligiblePermissions": [],
"hidden": false,
"amount": 1000
}
```
Note
Requires the [Economics](https://umod.org/plugins/economics) plugin to be installed. Without it, this reward type can’t pay out.
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| -------- | ------ | --------------------------------------------------------------------- |
| `type` | string | Exactly 'Economics' |
| `amount` | number | Amount of in-game currency granted. May be a decimal. Greater than 0. |
Shared fields (6)
| Field | Type | Description |
| ------------------------------------------------------------------------------ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Display name. May be empty to use the reward type's default name (recommended for most rewards). |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A short blurb of 1 to 3 sentences. May be empty. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `eligiblePermissions` [⭐ Premium](/editions/ "Premium feature (see Editions)") | string\[] | Permission strings that gate this reward, using any-of logic: a non-empty list grants the reward to any player holding at least one of the listed permissions. An empty array means everyone is eligible. |
| `hidden` | boolean | If true, players don't see this reward in the menu. It still pays out on claim and still makes the contract, objective or prestige tier claimable. |
## Notes
[Section titled “Notes”](#notes)
* `amount` may carry a decimal (e.g. `12.5`).
* The `amount` you set here is the base. The server’s [reward multiplier tiers](/configuration/rewards/#multipliers) can pay some players more than that.
* If `title` is left empty, the reward displays as **Money**.
Not the reward you’re looking for?
If you need something else that doesn’t look supported by Contracts, you may be able to use a [Command Reward](/contracts/rewards/command/) to run a command that gives your players the reward you want.
# Item
> Grant a quantity of a Rust item.
Grants a specified quantity of a Rust item to the player when the reward is claimed.
```json
{
"type": "Item",
"title": "",
"description": "",
"eligiblePermissions": [],
"hidden": false,
"item": "scrap",
"quantity": 250,
"skinId": null,
"itemName": null,
"itemNameTranslations": null
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| ------------------------------------------------------------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'Item' |
| `item` | string | Rust item shortNames, lowercase dotted (e.g. rifle.ak, rifle.bolt, wood, stones, metal.fragments, scrap, hqm, lowgradefuel). |
| `quantity` | integer (min 1) | Number of items granted. |
| `skinId` [⭐ Premium](/editions/ "Premium feature (see Editions)") | integer \| null (min 0) | Skin applied to the granted item, given as the skin's workshop ID. null for the item's default look. |
| `itemName` [⭐ Premium](/editions/ "Premium feature (see Editions)") | string \| null | Custom name to apply to the item. Separate from title, which names the reward in the menu. null or blank leaves the item's own name. |
| `itemNameTranslations` [⭐ Premium](/editions/ "Premium feature (see Editions)") | map \| null | Per-language overrides for itemName, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see itemName instead. null when there are none. |
Shared fields (6)
| Field | Type | Description |
| ------------------------------------------------------------------------------ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Display name. May be empty to use the reward type's default name (recommended for most rewards). |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A short blurb of 1 to 3 sentences. May be empty. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `eligiblePermissions` [⭐ Premium](/editions/ "Premium feature (see Editions)") | string\[] | Permission strings that gate this reward, using any-of logic: a non-empty list grants the reward to any player holding at least one of the listed permissions. An empty array means everyone is eligible. |
| `hidden` | boolean | If true, players don't see this reward in the menu. It still pays out on claim and still makes the contract, objective or prestige tier claimable. |
## Notes
[Section titled “Notes”](#notes)
* [⭐ Premium](/editions/ "Premium feature (see Editions)") `skinId`, `itemName` and `itemNameTranslations` only work on the Full edition. On Lite the item keeps its default look and name, both in the player’s inventory and in the menu. Your values stay in the file and start working when you upgrade.
* `item` must be a valid Rust item shortname (or item id), or the reward is treated as invalid.
* If `title` is left empty, the reward displays a default name.
* A skinned or named item does not stack with a plain one. That is the game’s own rule and figuring out custom stacking is out of scope for Contracts.
* `itemName` goes on the item itself, while `title` names the reward in the menu. Setting only `itemName` also gives the menu something better than the plain item name.
* If using `itemNameTranslations`, the item’s name will be translated for the player receiving the item. It will stay translated as-is and not re-translated if another player picks it up.
* [⭐ Premium](/editions/ "Premium feature (see Editions)") Paying the same skinned item on many contracts? Define it once as an [Item custom reward definition](/custom-rewards/#item-definitions) and reference it with a [Custom reward](/contracts/rewards/custom/).
Not the reward you’re looking for?
If you need something else that doesn’t look supported by Contracts, you may be able to use a [Command Reward](/contracts/rewards/command/) to run a command that gives your players the reward you want.
# Contract Score
> Grant Contract Score (CS), the plugin's own non-spendable points.
Grants an amount of Contract Score (CS) to the player when the reward is claimed. CS is the plugin’s own points system.
Note
CS ranks players on the [leaderboard](/leaderboard/) and climbs the [prestige track](/prestige/), where each tier pays out rewards you write yourself. Players can also view their CS with the `/contracts score` chat command.
```json
{
"type": "Score",
"title": "",
"description": "",
"eligiblePermissions": [],
"hidden": false,
"amount": 100
}
```
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| -------- | --------------- | -------------------------------------- |
| `type` | string | Exactly 'Score' |
| `amount` | integer (min 1) | Amount of Contract Score (CS) granted. |
Shared fields (6)
| Field | Type | Description |
| ------------------------------------------------------------------------------ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Display name. May be empty to use the reward type's default name (recommended for most rewards). |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A short blurb of 1 to 3 sentences. May be empty. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `eligiblePermissions` [⭐ Premium](/editions/ "Premium feature (see Editions)") | string\[] | Permission strings that gate this reward, using any-of logic: a non-empty list grants the reward to any player holding at least one of the listed permissions. An empty array means everyone is eligible. |
| `hidden` | boolean | If true, players don't see this reward in the menu. It still pays out on claim and still makes the contract, objective or prestige tier claimable. |
## Notes
[Section titled “Notes”](#notes)
* `amount` is a whole number of CS (no decimals) and must be at least 1.
* The `amount` you set here is the base. The server’s [reward multiplier tiers](/configuration/rewards/#multipliers) can pay some players more than that.
* If `title` is left empty, the reward displays as **Score**.
* A Score reward cannot be placed inside a [prestige tier](/prestige/), because the track is built out of CS in the first place. Every other reward type works there.
* Score is **not to be treated as a currency**. It is not spendable and will not become spendable. There’s already Economics and Server Rewards which are much better suited for that purpose, work with almost every other plugin and is free.
Not the reward you’re looking for?
If you need something else that doesn’t look supported by Contracts, you may be able to use a [Command Reward](/contracts/rewards/command/) to run a command that gives your players the reward you want.
# Server Rewards
> Grant RP via the ServerRewards plugin.
Grants an amount of RP (reward points) to the player when the reward is claimed.
```json
{
"type": "ServerRewards",
"title": "",
"description": "",
"eligiblePermissions": [],
"hidden": false,
"amount": 100
}
```
Note
Requires the [ServerRewards](https://umod.org/plugins/server-rewards) plugin to be installed. Without it, this reward type can’t pay out.
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| -------- | --------------- | --------------------------------------------- |
| `type` | string | Exactly 'ServerRewards' |
| `amount` | integer (min 1) | Amount of Server Rewards points (RP) granted. |
Shared fields (6)
| Field | Type | Description |
| ------------------------------------------------------------------------------ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Display name. May be empty to use the reward type's default name (recommended for most rewards). |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A short blurb of 1 to 3 sentences. May be empty. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `eligiblePermissions` [⭐ Premium](/editions/ "Premium feature (see Editions)") | string\[] | Permission strings that gate this reward, using any-of logic: a non-empty list grants the reward to any player holding at least one of the listed permissions. An empty array means everyone is eligible. |
| `hidden` | boolean | If true, players don't see this reward in the menu. It still pays out on claim and still makes the contract, objective or prestige tier claimable. |
## Notes
[Section titled “Notes”](#notes)
* `amount` is a whole number of RP (no decimals).
* The `amount` you set here is the base. The server’s [reward multiplier tiers](/configuration/rewards/#multipliers) can pay some players more than that.
* If `title` is left empty, the reward displays as **RP**.
Not the reward you’re looking for?
If you need something else that doesn’t look supported by Contracts, you may be able to use a [Command Reward](/contracts/rewards/command/) to run a command that gives your players the reward you want.
# Skill Tree Points
> Grant Skill Tree skill points via the Skill Tree plugin.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Grants skill points to the player when the reward is claimed. Points are what a player spends to unlock Skill Tree nodes, so this gives them something they can use right away instead of progress towards it. If you want the progress instead, use a [Skill Tree XP](/contracts/rewards/skill-tree-xp/) reward.
```json
{
"type": "SkillTreePoints",
"title": "",
"description": "",
"eligiblePermissions": [],
"hidden": false,
"amount": 1
}
```
Note
Requires the [Skill Tree](https://codefling.com/plugins/skill-tree) plugin to be installed. Without it, this reward is hidden in the menu and pays nothing.
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| -------- | --------------- | -------------------------------------------------------------- |
| `type` | string | Exactly 'SkillTreePoints' |
| `amount` | integer (min 1) | Number of Skill Tree skill points granted. Whole numbers only. |
Shared fields (6)
| Field | Type | Description |
| ------------------------------------------------------------------------------ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Display name. May be empty to use the reward type's default name (recommended for most rewards). |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A short blurb of 1 to 3 sentences. May be empty. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `eligiblePermissions` [⭐ Premium](/editions/ "Premium feature (see Editions)") | string\[] | Permission strings that gate this reward, using any-of logic: a non-empty list grants the reward to any player holding at least one of the listed permissions. An empty array means everyone is eligible. |
| `hidden` | boolean | If true, players don't see this reward in the menu. It still pays out on claim and still makes the contract, objective or prestige tier claimable. |
## Notes
[Section titled “Notes”](#notes)
* The `amount` you set here is the base. The server’s [reward multiplier tiers](/configuration/rewards/#multipliers) can pay some players more than that.
### This reward cannot be taken back
[Section titled “This reward cannot be taken back”](#this-reward-cannot-be-taken-back)
Skill Tree has no way to remove skill points a player already has, and a player can spend them the second they land. So when a claim fails halfway through, Contracts keeps everything it has already handed out and marks the contract claimed instead of rolling back.
Just like XP, for the reason stated above, Contracts distributes Skill Tree Points rewards as one of the last rewards in the distribution order, so that reversible rewards are processed first to minimize potential inconsistencies (though they can still happen in rare use-cases).
Not the reward you’re looking for?
If you need something else that doesn’t look supported by Contracts, you may be able to use a [Command Reward](/contracts/rewards/command/) to run a command that gives your players the reward you want.
# Skill Tree XP
> Grant Skill Tree XP via the Skill Tree plugin.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
Grants an amount of Skill Tree experience to the player when the reward is claimed. XP moves the player towards their next skill point. If you want to hand over the points themselves, use a [Skill Tree Points](/contracts/rewards/skill-tree-points/) reward.
```json
{
"type": "SkillTreeXp",
"title": "",
"description": "",
"eligiblePermissions": [],
"hidden": false,
"amount": 500,
"applyBonuses": false
}
```
Note
Requires the [Skill Tree](https://codefling.com/plugins/skill-tree) plugin to be installed. Without it, this reward is hidden in the menu and pays nothing.
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Description |
| -------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Exactly 'SkillTreeXp' |
| `amount` | number | Amount of Skill Tree experience granted. May be a decimal. Greater than 0. |
| `applyBonuses` | boolean | When true, Skill Tree scales the amount by the player's rested XP, prestige, permission and night-time modifiers, and other plugins can add to it. When false, Contracts hands over the amount as written. Either way Skill Tree's daily cap, XP debt and any server-wide XP modifier still apply, so what the player keeps can differ from what the menu shows. |
Shared fields (6)
| Field | Type | Description |
| ------------------------------------------------------------------------------ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | string | Display name. May be empty to use the reward type's default name (recommended for most rewards). |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string | A short blurb of 1 to 3 sentences. May be empty. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| `eligiblePermissions` [⭐ Premium](/editions/ "Premium feature (see Editions)") | string\[] | Permission strings that gate this reward, using any-of logic: a non-empty list grants the reward to any player holding at least one of the listed permissions. An empty array means everyone is eligible. |
| `hidden` | boolean | If true, players don't see this reward in the menu. It still pays out on claim and still makes the contract, objective or prestige tier claimable. |
## Notes
[Section titled “Notes”](#notes)
* The `amount` you set here is the base. The server’s [reward multiplier tiers](/configuration/rewards/#multipliers) can pay some players more than that.
* A player without the `skilltree.xp` permission can never gain XP, so the reward shows as ineligible for them and is never granted. Grant it the way Skill Tree’s own docs say, usually `o.grant group default skilltree.xp`.
### What the player gets can differ from the number shown
[Section titled “What the player gets can differ from the number shown”](#what-the-player-gets-can-differ-from-the-number-shown)
The amount on the card is what Contracts tells Skill Tree to give. Skill Tree decides what the player actually keeps and Contracts has no way of knowing the final amount.
`applyBonuses` can change that final number. The four below apply either way, **`applyBonuses` off included**:
* **The daily XP cap.** Skill Tree cuts the XP down to what is left of the player’s day. A player who has already hit the cap gets less, or nothing at all.
* **XP debt.** A player who owes XP pays the whole amount into the debt instead and gains nothing.
* **Server-wide XP modifiers.** Another plugin can set a global XP multiplier inside Skill Tree, and a running Skill Tree XP Event replaces the amount entirely.
* **Team XP sharing.** A player carrying Skill Tree’s XP share buff spreads part of the contract XP to nearby teammates.
### Reversal
[Section titled “Reversal”](#reversal)
When a claim fails halfway through, Contracts takes the XP back, but Skill Tree never drops a player below the start of their current level. If 500 XP pushed a level 2 player to level 3, the reversal will only take them back to the start of level 3, not all the way to level 2.
That being said, Contracts processes rewards in an order that favors reversible rewards first. Because of that described limitation, Contracts processes Skill Tree XP rewards as one of the last rewards in the distribution order. So this use-case should rarely happen in practice.
Not the reward you’re looking for?
If you need something else that doesn’t look supported by Contracts, you may be able to use a [Command Reward](/contracts/rewards/command/) to run a command that gives your players the reward you want.
# Custom Rewards
> Reusable console commands and items that Custom rewards reference by key.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
A **custom reward definition** is a reward you write once and reuse everywhere. It comes in two kinds:
* **Command** runs one or more console commands, which is how many plugins (Heli Signals, XPerience, Ganja) hand out their rewards.
* **Item** gives an item, with an optional skin and name. This is how you pay out a server currency made from an item.
Instead of retyping it on every contract, define it once here, then add a [Custom reward](/contracts/rewards/custom/) that references it by key with an amount.
The biggest advantage is reusable editing: fix the definition and every contract that references it is fixed too, including contracts you exported months ago. The plugin looks up the definition when the reward is claimed, not when the contract is authored.
Custom reward definitions are stored in the `custom_reward_data.json` data file and authored with the [web editor](https://editor.rustcontracts.com).
Caution
A Command definition only names a command. If this is another plugin’s command, that plugin must be installed on the server for the reward to work properly. Contracts will not check for the plugin’s presence (because commands don’t necessarily come from plugins), and the reward will silently pass through without error.
## Shape
[Section titled “Shape”](#shape)
Every definition has a `kind`, a display name and an optional icon, stored under a key you choose. A Command definition adds the commands to run, plus optional reverse commands and a unit label. An Item definition adds the item, an optional skin ID and an optional item name.
Unverified Examples
These command examples are based on popular plugins, which I don’t own. Their definitions are solely based off the documentation on their plugin pages at the time of writing and may not be accurate. Feel free to flag any errors you find in these examples on Discord!
```json
{
"customRewards": {
"EasyHeliSignal": {
"kind": "Command",
"displayName": "Easy Heli Signal",
"displayNameTranslations": null,
"commands": [
"hsgive easy {playerId} {amount}"
],
"reverseCommands": [],
"unitLabel": null,
"unitLabelTranslations": null,
"icon": null,
"iconColor": null,
"iconRaw": false
},
"Weed": {
"kind": "Command",
"displayName": "Weed",
"displayNameTranslations": null,
"commands": [
"ganja.give weed low_quality {amount} {playerId}"
],
"reverseCommands": [],
"unitLabel": null,
"unitLabelTranslations": null,
"icon": null,
"iconColor": null,
"iconRaw": false
},
"Cash": {
"kind": "Item",
"displayName": "Cash",
"displayNameTranslations": null,
"item": "paper",
"skinId": 3731371060,
"itemName": "Cash",
"itemNameTranslations": null,
"icon": null,
"iconColor": null,
"iconRaw": false
}
},
"version": {
"Major": 1,
"Minor": 16,
"Patch": 1
}
}
```
The key (`EasyHeliSignal`) is the identifier that Custom rewards reference. `displayName` is the text players see when a reward using the definition sets no title of its own, and `displayNameTranslations` holds optional per-language overrides for it (see [Translating your content](/guides/translating-your-content/)).
## Item definitions
[Section titled “Item definitions”](#item-definitions)
Item definitions are made for server currencies: an item with a skin and a custom name that most of your contracts pay out. The example above is paper with workshop skin `3731371060`, named `Cash`. With an [Item reward](/contracts/rewards/item/) you would repeat the item, skin and name on every contract. With an Item definition you write them once, and each Custom reward that references it gives `amount` of that item.
A Custom reward using an Item definition pays like an Item reward:
* When the player’s inventory is full, the claim stops. The player needs to make some room and claim again. When the plugin gives the reward on its own, for example when the contract expires, the items drop at the player’s feet instead.
* If claiming fails halfway, the plugin takes the items back, like any other item reward.
* The menu shows the item with its skin and the item count. Set `icon` to show your own image instead.
* A skin sold by Facepunch only goes on the item for players who own it. Other players get the plain item, which does not stack with the skinned one. The plugin warns at load when a definition uses one of these skins.
`itemName` goes on the item itself, while `displayName` names the reward in the menu. Leave `itemName` null to keep the game’s name.
An Item definition has no `commands`, `reverseCommands` or `unitLabel`. If you add them by hand, the plugin removes them with a warning. A definition whose `item` is not a real Rust item is removed at load, and the rewards using it act like a [missing definition](#missing-definitions).
To give the same item from a shop or a kit, use [`contracts.give_item`](/commands/#giving-items-from-a-shop-or-kit).
To make an objective match the same item, write it as `paper:3731371060` in the objective’s items list. See [Targeting custom items](/guides/custom-items/).
Multipliers apply
Unlike an Item reward, the item count follows the server’s [reward multiplier tiers](/configuration/rewards/#multipliers), through the definition’s key under `Custom`.
## Plugins you might use
[Section titled “Plugins you might use”](#plugins-you-might-use)
Anything with a console command that takes a player works for a Command definition. The ones that come up most are Heli Signals, XPerience, ZLevels Remastered, Ganja, Kits, and battlepass or leaderboard plugins. Read the command off that plugin’s own page, then drop `{playerId}` and `{amount}` into it. Contracts does not check that the plugin is installed, so test the command in the server console first.
Paying out an item? Use an [Item definition](#item-definitions).
## Fields
[Section titled “Fields”](#fields)
### Command
[Section titled “Command”](#command)
| Field | Type | Description |
| ----------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `kind` | string | Exactly 'Command' |
| `commands` | string\[] | Console commands to run when the reward is claimed, top to bottom. Every command supports runtime placeholders such as {playerId} and {playerName}. Include {amount} in any of them to make every usage carry an amount that substitutes into it. |
| `reverseCommands` | string\[] | Console commands that take back what the commands gave, with the same placeholders. They run in the order written, and only when claiming fails halfway and the rewards already given roll back. Empty when the reward cannot be taken back. |
| `unitLabel` | string \| null | Short unit shown beside the amount, like "XP". Only valid when the command contains {amount}. Null shows the amount alone. |
| `unitLabelTranslations` | map \| null | Per-language overrides for unitLabel, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see unitLabel instead. null when there are none. |
Shared fields (5)
| Field | Type | Description |
| ------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `displayName` | string | Label shown on the reward when a usage sets no title. The record key stays the identifier Custom rewards reference. |
| `displayNameTranslations` | map \| null | Per-language overrides for displayName, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see displayName instead. null when there are none. |
| `icon` | string \| null | Direct image URL for the icon on rewards using this definition. A reward's own icon beats it. Null (or omitted) shows the item for an Item definition. For a Command definition it keeps the default lightning icon, or the per-type Custom override from the plugin config when one is set. |
| `iconColor` | string \| null | Color for the icon, as hex (#rrggbb or #rrggbbaa), rgb() or rgba(). Null (or omitted) inherits the per-type or default color. Invalid colors warn at plugin load and render white. Does nothing when an Item definition shows its item. |
| `iconRaw` | boolean | Set true when the icon is a full-color image (a logo, custom art) so it renders in its own colors. When false the image renders as a solid color, which is right for monochrome icons. |
### Item
[Section titled “Item”](#item)
| Field | Type | Description |
| ---------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `kind` | string | Exactly 'Item' |
| `item` | string | Rust item shortNames, lowercase dotted (e.g. rifle.ak, rifle.bolt, wood, stones, metal.fragments, scrap, hqm, lowgradefuel). |
| `skinId` | integer \| null (min 0) | Skin put on the given items, as the skin's workshop ID. null for the item's default look. A skin sold by Facepunch only shows for players who own it. Other players get the item without the skin, and those items do not stack with skinned ones. |
| `itemName` | string \| null | Custom name put on the given items. Separate from displayName, which names the reward in the menu. null or blank leaves the item's own name. |
| `itemNameTranslations` | map \| null | Per-language overrides for itemName, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see itemName instead. null when there are none. |
Shared fields (5)
| Field | Type | Description |
| ------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `displayName` | string | Label shown on the reward when a usage sets no title. The record key stays the identifier Custom rewards reference. |
| `displayNameTranslations` | map \| null | Per-language overrides for displayName, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see displayName instead. null when there are none. |
| `icon` | string \| null | Direct image URL for the icon on rewards using this definition. A reward's own icon beats it. Null (or omitted) shows the item for an Item definition. For a Command definition it keeps the default lightning icon, or the per-type Custom override from the plugin config when one is set. |
| `iconColor` | string \| null | Color for the icon, as hex (#rrggbb or #rrggbbaa), rgb() or rgba(). Null (or omitted) inherits the per-type or default color. Invalid colors warn at plugin load and render white. Does nothing when an Item definition shows its item. |
| `iconRaw` | boolean | Set true when the icon is a full-color image (a logo, custom art) so it renders in its own colors. When false the image renders as a solid color, which is right for monochrome icons. |
## Referencing a definition
[Section titled “Referencing a definition”](#referencing-a-definition)
A [Custom reward](/contracts/rewards/custom/) on a contract or objective points at a definition with its `reward` field:
```json
{
"type": "Custom",
"title": "",
"description": "",
"eligiblePermissions": [],
"hidden": false,
"reward": "Weed",
"amount": 500,
"icon": null,
"iconColor": null,
"iconRaw": false
}
```
When this reward is claimed, the plugin substitutes `{amount}` with `500` and runs `ganja.give weed low_quality 500 {playerId}` on the server console. With `"reward": "Cash"` the same reward gives 500 Cash instead.
## Placeholders
[Section titled “Placeholders”](#placeholders)
Command definitions only. Every command and reverse command takes every placeholder from the [Command reward table](/contracts/rewards/command/#placeholders), plus `{amount}`, which is replaced with the amount set on the reward that references the definition.
Put `{amount}` in any of the commands and every reward using the definition needs an amount. A reverse command or a unit label can only use the amount when at least one command takes it.
## Reverse commands
[Section titled “Reverse commands”](#reverse-commands)
Console commands normally cannot be taken back, so a failure halfway through claiming a multi-reward contract would leave players keeping partial payouts. The `reverseCommands` list fixes that: it holds the commands that undo `commands`, in the order they should run. When giving out rewards fails halfway during a claim, the plugin runs the reverse commands for each command reward it already gave, with the same placeholder substitution, and the player can claim again later.
Leave it empty (`[]`) when the reward cannot be undone. Plain [Command rewards](/contracts/rewards/command/#reverse-commands) can carry their own `reverseCommands` too.
An Item definition needs no reverse command. The plugin takes the items back on its own.
## Missing definitions
[Section titled “Missing definitions”](#missing-definitions)
A Custom reward can point at a definition that no longer exists, for example after a delete or a typo in a hand-edited file. In the editor this shows a warning, and export still works. On the server the plugin logs a warning at load and shows the reward as ineligible to every player, so the contract stays playable and only that reward is never granted.
# Editions
> What the Lite edition of Contracts includes, how premium content behaves on it, and how upgrading works.
Contracts comes in two editions, **Lite** and **Full** (sometimes referred to as “Premium”). Both are built from the same plugin, carry the same version, and read the same config and data files. They differ only in which features actually run.
[Get the Lite edition free](https://get.rustcontracts.com)
If you run the Lite edition, turn on the highlight below: every premium feature across the docs gets a **⭐ Premium** badge, so you can see at a glance what your edition excludes. It is off by default, and the lists on this page show the full split either way.
\[ ]Highlight premium features (⭐ Premium) across the docs
In the web editor, you can also turn on Lite highlighting in the settings menu (bottom left cog wheel icon). This is useful if you want to see what your edition excludes while editing contracts. You can still use Premium features with the Lite highlighting on.
## Upgrading is one file
[Section titled “Upgrading is one file”](#upgrading-is-one-file)
The Lite and Full editions share every config and data file, so upgrading takes no reconfiguration. Overwrite your server’s `Contracts.cs` with the Full build and reload the plugin: every premium feature already present in your config activates on the spot. Contracts you authored on Lite, premium rewards, HUD settings, category caps, all of it comes to life with nothing to redo.
You can [get the Full edition here](https://buy.rustcontracts.com/).
## What the Lite edition includes
[Section titled “What the Lite edition includes”](#what-the-lite-edition-includes)
Here’s what Lite includes:
* **Objectives:** [Kill](/contracts/objectives/kill/), [Gather](/contracts/objectives/gather/), [Loot](/contracts/objectives/loot/), [Turn In](/contracts/objectives/turn-in/), [Craft](/contracts/objectives/craft/), [Plant](/contracts/objectives/plant/).
* **Conditions:** [Weapon](/contracts/conditions/weapon/), [Time of Day](/contracts/conditions/time-of-day/), [Hit Area](/contracts/conditions/hit-area/), [Biome](/contracts/conditions/biome/), and the logical conditions [And, Or, and Not](/contracts/conditions/#combining-conditions).
* **Rewards:** [Item](/contracts/rewards/item/), [Economics](/contracts/rewards/economics/), [ServerRewards](/contracts/rewards/server-rewards/), and [Score](/contracts/rewards/score/), without reward permissions, and without item reward skins or custom item names. Skinned entries in item lists still work.
* **Logging events:** Contract Accepted, Contract Completed, and Contract Abandoned.
* **[Reminders](/configuration/reminders/):** both reminder types, with every trigger and method.
Other **excluded** features include:
* the [HUD](/configuration/hud/)
* the [leaderboard](/leaderboard/)
* per-category permission caps
* reward permissions
* [reward multipliers](/configuration/rewards/#multipliers)
* the [prestige track](/prestige/), including tier locks on contracts and categories
## What premium content does on Lite
[Section titled “What premium content does on Lite”](#what-premium-content-does-on-lite)
Nothing you author on Lite is lost or rejected. Premium content stays in your files untouched and simply sits inactive until you upgrade. A Lite server is indistinguishable to players from a Full server where no one holds a premium grant: there is no upsell, no locked-feature message, no sign that the edition is reduced.
* **Contracts that use a premium objective, condition, or an objective [cooldown](/contracts/objectives/cooldown/) or [burst](/contracts/objectives/burst/) are locked.** They are never drawn into a category’s rotation and can’t be accepted, so players never see them. The contract’s definition is preserved exactly.
* **Contracts with [costs](/contracts/#purchasable-contracts) are locked the same way.** They are not handed out for free on Lite. The `costs` stay in the file and start working when you upgrade.
* **Command rewards, Custom rewards, rewards with reward permissions, and rewards attached to an objective are never granted.** They appear through the normal ineligible-reward treatment, the same as a reward a player doesn’t qualify for. The rest of the contract stays fully playable.
* **Item reward skins and custom item names do nothing.** The item keeps its default look and name, both in the player’s inventory and in the menu.
* **Per-category contract caps are not enforced.** A category behaves as it does for a player who holds none of its listed permissions (uncapped at the category level, though the global cap still binds).
* **The HUD is absent.** There is no on-screen tracker and no HUD-related commands or limits.
* **The leaderboard is absent.** The menu header has no board button. Score rewards still pay out and `/contracts score` still works.
* **The prestige track is absent.** The menu header has no prestige button and no tier pays rewards. A contract or category with `minPrestigeTier` set is locked the same way as a contract with premium content. Your `prestige_data.json` stays untouched and starts working when you upgrade.
* **Reward multipliers are all 1.** Every reward pays the amount the contract asks for. The `Multipliers` block stays in your config and starts working when you upgrade.
* **Premium logging events are not written.** Only Contract Accepted, Completed, and Abandoned are recorded.
## Will there be more premium features coming to Lite?
[Section titled “Will there be more premium features coming to Lite?”](#will-there-be-more-premium-features-coming-to-lite)
Occasionally, but don’t count on it. Lite is a demo, not a free tier: it exists so you can watch Contracts run on your own server, with your own players, before deciding whether to buy. It’s built for server owners who want proof before they pay, and that’s exactly what it delivers.
The feature set is curated for that purpose. A feature only moves into Lite when it makes the demo better, never to turn Lite into a long-term alternative to Full. So judge Lite by what it shows, not by what it holds back: it proves the plugin is solid and that your players enjoy it, while the depth (the HUD, the full roster of objectives, conditions, and rewards, and everything else listed above) stays in Full.
If contracts are already earning a place on your server, that’s your answer. [Upgrading is one file](#upgrading-is-one-file): everything you’ve built on Lite carries over, and [the Full edition](https://buy.rustcontracts.com/) is where Contracts really gets to show off.
## How premium features are gated
[Section titled “How premium features are gated”](#how-premium-features-are-gated)
You might be tempted to flip a few booleans in the plugin’s code to make premium features work on Lite. Don’t: it won’t work, and it may have unintended consequences.
When the plugin is compiled for release, premium code is literally **stripped out** of the Lite edition using C# preprocessor directives. The functionality isn’t hidden, it simply doesn’t exist.
Some code paths use explicit edition checks instead. These don’t gate premium code, they keep the Lite edition from executing or displaying premium code paths that lead to dead ends. Removing them won’t make premium features work, it will just make the Lite edition crash or misbehave.
Other code paths reference premium features, but the code that makes them work isn’t present in the Lite edition. For example, the C# classes that define premium objective types and conditions exist in the Lite edition, but the code that defines their behavior does not. This lets the Lite edition coexist with **premium data files**, so upgrading to Full is literally just swapping in the Full `Contracts.cs` and reloading the plugin. Without these class definitions, premium data files on Lite could crash the plugin or make it delete data files.
# Configure the plugin
> Where the Contracts settings file lives and how to reload it after changes.
Contracts works out of the box, so this step is optional. When you want to change the command, permissions, HUD, or logging, edit the settings file:
* Oxide
`oxide/config/Contracts.json`
* Carbon
`carbon/config/Contracts.json`
Then reload the plugin so the changes take effect:
* Oxide
```text
o.reload Contracts
```
* Carbon
```text
c.reload Contracts
```
Every setting in this file is documented in the [Configuration](/configuration/) section. You can edit the file by hand, or open it in the [web editor](https://editor.rustcontracts.com)’s config section, which validates every setting as you change it. Your contracts, categories, and presets are managed in the web editor too. Use it for those instead of editing the data files by hand: it validates as you type, and a hand-edited data file is easy to break with a syntax error or a bad value.
# Load the default data
> What ships out of the box and how Contracts stores its config and data files.
You don’t have to author anything to get started. On first load, Contracts writes a full set of **default data**: ready-to-play contracts grouped into four rotating categories: hourly, daily, weekly and wipe.
## Default data
[Section titled “Default data”](#default-data)
### Default Contracts
[Section titled “Default Contracts”](#default-contracts)
> [What’s a contract?](/concepts/#contract)
There are a total of **99 contracts** that ship with the plugin by default, spread across the four default categories listed below. The default contracts are designed purposefully to touch a bit of every feature of the plugin.
The breakdown by objective type. They may total more than 99 because some contracts have multiple objectives of different types:
| Objective type | Contracts |
| -------------- | --------- |
| Kill | 19 |
| Gather | 8 |
| Loot | 7 |
| Fishing | 6 |
| Harvest | 6 |
| Craft | 5 |
| Damage | 5 |
| CardSwipe | 4 |
| CrateHack | 4 |
| Gamble | 4 |
| Heal | 4 |
| Travel | 4 |
| Cloning | 3 |
| Cook | 3 |
| Dig | 3 |
| Plant | 3 |
| Playtime | 3 |
| TurnIn | 3 |
| Visit | 3 |
| Mix | 2 |
| Recycle | 1 |
| Repair | 1 |
### Default Categories
[Section titled “Default Categories”](#default-categories)
> [What’s a category?](/concepts/#category)
The defaults populate four categories, each rotating on its own timer:
| Category | Contracts | Rotates every | Theme |
| -------------------- | --------- | ------------------ | ------------------------------- |
| **Hourly Contracts** | 19 | 1 hour | Quick, easy contracts |
| **Daily Contracts** | 47 | 24 hours | Achievable mid-effort contracts |
| **Weekly Contracts** | 26 | 7 days | Harder, higher-value contracts |
| **Wipe Contracts** | 7 | Never (whole wipe) | Long-term prestige goals |
### Default Presets
[Section titled “Default Presets”](#default-presets)
> [What’s a preset?](/concepts/#preset)
The defaults also include **36 presets**, though apart from the `@scientists` and `@animals` presets, most are just examples to support the default contracts, not something you’ll likely re-use.
# Create your first contract
> The anatomy of a contract and a minimal working example you can drop into your config.
> It’s strongly recommended to use the [web editor](https://editor.rustcontracts.com) to manage your contracts, but if you want to write one by hand, this page shows you how. Keep in mind that data files are not meant to be edited by hand, so the shape and complexity of these JSON files were not designed to be user-friendly to edit like the [config file](/configuration/).
A **contract** is a small bundle: one or more **objectives** to complete and one or more **rewards** to pay out. Contracts are stored in the `contracts_data.json` data file and surfaced to players by being listed in a **category** (`contract_category_data.json`).
AI Generated Contracts
You can try generating contracts with AI using this [AI prompt](/ai-authoring/#the-prompt). Paste the response into the [web editor’s](https://editor.rustcontracts.com/) “Generate” feature to automatically create a contract from the AI’s response.
## Anatomy of a contract
[Section titled “Anatomy of a contract”](#anatomy-of-a-contract)
Every contract has:
* **`title`**: the name players see. Required, non-empty.
* **`description`**: a short blurb. May be empty if the title says it all.
* **`objectives`**: a map of one or more [objectives](/contracts/objectives/). At least one is required.
* **`rewards`**: a map of one or more [rewards](/contracts/rewards/). At least one is required.
* **`progressionType`**: how the objectives unlock:
* `Independent`: all objectives are active at once (the default).
* `Sequential`: objectives must be completed in order, so only the first incomplete one is active.
* `Progressive`: like `Sequential`, but locked objectives are hidden in the menu.
Objectives and rewards are stored as **keyed maps**, not arrays. Each key is a unique id you choose, and the key order is the display order.
## A minimal example
[Section titled “A minimal example”](#a-minimal-example)
This contract has a single objective: to kill five chickens. It then rewards the player with some cooked chicken, a bit of Economics cash, and some RP:
```json
{
"contracts": {
"chicken_dinner": {
"title": "Chicken Dinner",
"description": "Hunt down chickens for an easy meal.",
"progressionType": "Independent",
"objectives": {
"chicken_dinner_kill_chickens": {
"type": "Kill",
"title": "Kill chickens",
"description": "Find and kill chickens around the island.",
"conditions": {},
"amountRequired": 5,
"entities": [
"chicken"
]
}
},
"rewards": {
"chicken_dinner_reward_chicken": {
"type": "Item",
"title": "",
"description": "",
"eligiblePermissions": [],
"hidden": false,
"item": "chicken.cooked",
"quantity": 5
},
"chicken_dinner_reward_economics": {
"type": "Economics",
"title": "",
"description": "",
"eligiblePermissions": [],
"hidden": false,
"amount": 25
},
"chicken_dinner_reward_server_rewards": {
"type": "ServerRewards",
"title": "",
"description": "",
"eligiblePermissions": [],
"hidden": false,
"amount": 1
},
"chicken_dinner_reward_score": {
"type": "Score",
"title": "",
"description": "",
"eligiblePermissions": [],
"hidden": false,
"amount": 10
}
}
}
},
"version": {
"Major": 1,
"Minor": 16,
"Patch": 1
}
}
```
A few things to note from the example:
* `type` selects which [objective](/contracts/objectives/) or [reward](/contracts/rewards/) this is, and which extra fields it needs.
* `amountRequired` is how many times the objective must be met (here, five kills).
* `entities` and `item` use Rust **shortnames** (`chicken`, `chicken.cooked`), not display names.
* Leaving a reward `title` empty uses that reward type’s default name.
* `conditions` is empty here. See [Conditions](/contracts/conditions/) to restrict *when* progress counts (e.g. only at night, only with a bow).
## Make it appear in the menu
[Section titled “Make it appear in the menu”](#make-it-appear-in-the-menu)
A contract isn’t shown until a category lists it. Add its id to a category’s `contractIds` in the `categories` file:
```json
{
"categories": {
"hourly": {
"contractIds": ["chicken_dinner", "..."]
}
}
}
```
Then reload the plugin or re-import your files. The contract joins that category’s rotation pool and will be a candidate to show up in the menu when that category rotates.
* Oxide
```text
o.reload Contracts
```
* Carbon
```text
c.reload Contracts
```
> Don’t want to wait for the next rotation? Check out how to [add a contract to an active rotation](/guides/add-contract-to-active-rotation/).
# Installation
> Install the Contracts plugin on an Oxide or Carbon Rust server and grant access.
> Don’t have Contracts yet? [Get the plugin!](https://buy.rustcontracts.com/)
Contracts is a Rust server plugin. It runs on both **Oxide** and **Carbon**. The same `Contracts.cs` file works on either framework.
Quickstart
Already comfortable with Oxide/Carbon? Copy `Contracts.cs` into `oxide/plugins/` (or `carbon/plugins/`), install [ImageLibrary](https://umod.org/plugins/image-library), then run `o.grant group default contracts.use` and open the menu with `/contracts`. The sections below walk through the same steps in detail.
## 1. Download the plugin
[Section titled “1. Download the plugin”](#1-download-the-plugin)
You can download the plugin [here](https://buy.rustcontracts.com/).
The download is a `.zip` file containing the plugin and default data files. Extract the folder to your computer and you’ll find the following files. The folder structure matches your server’s `oxide` or `carbon` folder, so you can copy the files directly into your server’s root folder.
* plugins
* Contracts.cs The plugin file.
* data
* Contracts
* contracts\_data.json Contains your contracts
* contract\_category\_data.json Contains hourly/daily/weekly/wipe categories with assigned contracts
* preset\_data.json Contains presets used by the contracts
* custom\_reward\_data.json Contains custom reward definitions (empty by default)
* prestige\_data.json Contains the prestige track (empty by default)
Tip
While you’ll typically only need to copy the `data/Contracts` files once on your initial setup, these data files can be useful for you in future updates too! If new default contracts are added in a future version, you can:
1. Load your current data files into the [Web editor](https://editor.rustcontracts.com/)
2. Drop the bundled data files into the editor
3. A modal will appear and let you selectively merge new contracts/categories/presets into your existing data files, **without overwriting your existing data**!
Learn more about [what is included](/getting-started/default-data/).
## 2. Install dependencies
[Section titled “2. Install dependencies”](#2-install-dependencies)
Contracts has a few required and optional dependencies. Required dependencies must be installed for the plugin to work. Optional dependencies are only needed if you want to use certain features.
| Plugin | Needed for | Required? |
| ----------------------------------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| [ImageLibrary](https://umod.org/plugins/image-library) | Icons and images in the in-game menu. Install this before the first load. | Yes |
| [Economics](https://umod.org/plugins/economics) | [Economics rewards](/contracts/rewards/economics/) | Only if you grant Economics rewards |
| [ServerRewards](https://umod.org/plugins/server-rewards) | [ServerRewards rewards](/contracts/rewards/server-rewards/) | Only if you grant RP |
| [Monument Finder](https://umod.org/plugins/monument-finder) | [Monument Objective Condition](/contracts/conditions/monument/) | Only if you want to use the Monument Condition (which some default contracts use) |
| [Skill Tree](https://codefling.com/plugins/skill-tree) | [Skill Tree XP rewards](/contracts/rewards/skill-tree-xp/) | Only if you grant Skill Tree XP |
Note
You can have Economics and ServerRewards rewards configured in your contracts without having the plugin installed. At the time of reward distribution, these rewards will be skipped. This makes it easier to use shared contracts!
## 3. Install the plugin
[Section titled “3. Install the plugin”](#3-install-the-plugin)
Copy `plugins/Contracts.cs` into your server’s plugins directory:
* Oxide
`oxide/plugins/`
* Carbon
`carbon/plugins/`
**Optionally**, you can also copy the `data/Contracts` folder into your server’s data directory. If you don’t, the plugin will create these same files on first load. You may also pre-configure your data in the [Web editor](https://editor.rustcontracts.com/) and copy the files into your server’s data folder, so your server starts with your pre-configured contracts.
* Oxide
`oxide/data/`
* Carbon
`carbon/data/`
Unless you’ve configured your server’s framework differently, the plugin compiles and loads automatically. Otherwise you can load it manually:
* Oxide
```text
o.reload Contracts
```
* Carbon
```text
c.reload Contracts
```
On first load it writes its configuration, language and loads the [default data](/getting-started/default-data/).
## 4. Grant the permission
[Section titled “4. Grant the permission”](#4-grant-the-permission)
Players need the `contracts.use` permission to open the menu (and progress through contracts). Grant it to a group (for example the default `default` group, so everyone gets it):
* Oxide
```text
o.grant group default contracts.use
```
* Carbon
```text
c.grant group default contracts.use
```
Server admins should also hold `contracts.admin` for the management console commands.
| Permission | Grants |
| ----------------- | --------------------------------------------------- |
| `contracts.use` | Open the menu and complete contracts |
| `contracts.admin` | Run admin console commands (rotate, reset progress) |
The exact permission strings are configurable in the plugin config under **Permissions**. The permissions shown above are the defaults.
## 5. Open the menu
[Section titled “5. Open the menu”](#5-open-the-menu)
You’re all set! Players can now open the menu with the default chat command: `/contracts`. Type this in chat and you should see the menu appear. Players can also click the CONTRACTS button next to MISSIONS on their inventory screen (see [Menu Shortcut](/configuration/ui/#menu-shortcut)).
You can also configure a [custom chat command](/configuration/command/) in the plugin’s config.
Inform your players
Make sure to inform your players about the [plugin’s commands](/commands/) somewhere (such as your welcome panel) so they know how to use Contracts!
# Guides
> Recipes and how-tos for going out of your way with Contracts, beyond the day-to-day feature reference.
Guides are task-oriented recipes for doing something specific with Contracts that goes beyond the everyday authoring covered by the feature reference. Each one walks a goal step by step and links out to the reference pages for the details.
[Add a contract to an active rotation](/guides/add-contract-to-active-rotation/)How to make a contract go live in a category that has already rotated, without waiting for its next cycle.
[Targeting custom items](/guides/custom-items/)How to make an objective or condition match a custom item built from a base item and a skin, instead of the plain item.
[Edit contracts mid-rotation](/guides/edit-contracts-mid-rotation/)Learn what happens when you edit a contract while it is already in an active category rotation and what to expect.
[Getting item & entity names](/guides/getting-item-and-entity-names/)Learn how to find the correct item and entity names to use in your contracts
[Translating your content](/guides/translating-your-content/)Give contract titles, descriptions, category names, objective and reward text, and preset display names per-language translations that players see in their own language.
# Add a contract to an active rotation
> How to make a contract go live in a category that has already rotated, without waiting for its next cycle.
A category’s **active rotation** is the set of contracts it has currently drawn and is showing to players for this cycle. When a category rotates, it randomly draws a fresh set from its [contract list](/categories/) and players see those contracts until the next rotation.
Contracts is not designed to have an active rotation changed on the fly, and doing so is discouraged for the sake of your players’ experience. But if you have just added a contract and want it to show up before the next rotation comes around, here are your options, from least to most disruptive.
Prerequisite
The contract must already exist (see [Make it appear in the menu](/getting-started/first-contract/#make-it-appear-in-the-menu)). It also needs to be in the target category’s contract list, but Option 2 below adds it there for you if it is missing.
## Option 1: Wait for the next rotation (recommended)
[Section titled “Option 1: Wait for the next rotation (recommended)”](#option-1-wait-for-the-next-rotation-recommended)
The simplest and safest option is to do nothing. Once the contract is in the category’s contract list, it becomes a **candidate** the next time that category rotates. No player loses progress and no files are touched. If the category rotates hourly or daily, this is almost always the right call.
The trade-off is timing: you cannot control exactly when your contract appears, and because each rotation draws a random set from the list, it is not guaranteed to be picked on the very next cycle either.
## Option 2: Insert the contract with a command
[Section titled “Option 2: Insert the contract with a command”](#option-2-insert-the-contract-with-a-command)
This is the only option that puts a **specific** contract live right now without re-rolling the rest, and it needs no downtime or file editing. Run the admin command (see [Commands](/commands/)):
```text
contracts.add_to_rotation
```
The contract appears in the active rotation immediately and stays live until the category next rotates, when the set is re-drawn as usual. If the category is **dormant** (it had no contracts and so never started a rotation), this command starts a fresh rotation with your contract. No player loses progress, since this only adds to the active set. To pull a contract back out again, use `contracts.remove_from_rotation`. A [permanent contract](/categories/#permanent-contracts) can be pulled out this way too, but it comes back at the next rotation. Take it out of the category’s permanent list to keep it out.
Important!
If the contract is **not yet in the category’s contract list**, the command **adds it** to the category’s `contractIds` list. This means if you rely on the Web editor to be in sync with your server files, they will no longer match after this command.
## Option 3: Force the category to re-roll
[Section titled “Option 3: Force the category to re-roll”](#option-3-force-the-category-to-re-roll)
You can end the current rotation immediately and start a fresh one:
```text
contracts.rotate_category
```
This wipes progress and is still random
Rotating a category manually is disruptive to players:
* Every player **loses progress** on any in-progress contract in that category. The active set is replaced entirely, as if the category’s timer reached zero.
* The new set is drawn **at random** from the contract list, so your specific contract still might not be chosen (especially in a large list).
Use this only when a fresh rotation is acceptable for everyone, not to surgically insert one contract.
# Targeting custom items
> How to make an objective or condition match a custom item built from a base item and a skin, instead of the plain item.
Many servers hand out custom items: a currency, a token another plugin reads, special gear. Under the hood a custom item is almost always a normal Rust item with a workshop skin on it, and usually a custom name. Loot tables, shops and kits all create it that way.
Item lists match on the short name by default, so `"items": ["paper"]` counts plain paper and your Cash skin alike. To match only the custom item, add the skin’s workshop ID after a colon (`:`).
## Writing a skinned entry
[Section titled “Writing a skinned entry”](#writing-a-skinned-entry)
Every field that takes an item list accepts three forms:
| Entry | Matches |
| ------------------ | -------------------------------------- |
| `paper` | Paper with any skin, including none. |
| `paper:3731371060` | Only paper carrying exactly that skin. |
| `paper:0` | Only plain paper with no skin. |
You can list the same item more than once. `["rhib", "rhib:123"]` is the same as `rhib` alone, and `["rhib:123", "rhib:456"]` matches either custom item. A [preset](/presets/) can hold plain and skinned values.
This works on most objectives and conditions that take an item list, on the Lite edition too. Presets take it in their `values`. The only exception is a reward’s or cost’s `item` field, which names one item to give or take and has its own `skinId` field instead.
## Example: loot a custom currency
[Section titled “Example: loot a custom currency”](#example-loot-a-custom-currency)
Say your loot tables put Cash in crates: a paper item (`paper`) with skin `3731371060`. Define a preset with one skinned value, so the requirement chip in the menu reads the preset’s name:
```json
{
"Cash": {
"displayName": "Cash",
"displayNameTranslations": null,
"values": ["paper:3731371060"],
"icon": null,
"iconColor": null,
"iconRaw": false
}
}
```
Then reference it from the objective:
```json
{
"type": "Loot",
"title": "Loot 100 Cash",
"description": "",
"amountRequired": 100,
"conditions": {},
"rewards": {},
"items": ["@Cash"],
"containers": []
}
```
Skin name not shown
Writing `"items": ["paper:3731371060"]` directly works the same, but the chip then shows “Paper” with the skinned icon instead of “Cash”. The plugin doesn’t infer the intended display name from the skinned entry. The preset’s `displayName` is what labels it (and an icon, if provided).
## Finding the skin’s workshop ID
[Section titled “Finding the skin’s workshop ID”](#finding-the-skins-workshop-id)
The workshop ID is the number in the skin’s Steam Workshop URL. For `steamcommunity.com/sharedfiles/filedetails/?id=3731371060` the ID is `3731371060`.
A DLC or Twitch item that the game turns into its own item (the ice AK is `rifle.ak.ice`) is not a skin on the base item. Use its own short name as a plain entry.
## Limitations
[Section titled “Limitations”](#limitations)
The following list shows cases where the plugin cannot see a skin. Some of the things the plugin counts never reach it as an item, so it cannot read a skin there and treats them as unskinned:
* [Loot](/contracts/objectives/loot/) and [Harvest](/contracts/objectives/harvest/): picking a wild plant, wood, ore, mushroom or berry off the ground (internally referred to as “collectibles”).
* [Mix](/contracts/objectives/mix/), [Cloning](/contracts/objectives/cloning/), the `fish` list on [Fishing](/contracts/objectives/fishing/), and [Recycle](/contracts/objectives/recycle/) output.
* [Weapon](/contracts/conditions/weapon/) condition: hits from rockets, thrown explosives, arrows and fire. A held gun or melee weapon is fine.
On those, `hemp:123` never counts and `hemp:0` always does. Mix, Cloning, the `fish` list and Recycle output cannot see a skin at all, so the editor warns on a skinned entry there and the plugin prints a warning at load, but keeps the entry.
[Gather](/contracts/objectives/gather/) counts what the tree or node itself hands out. A custom item another plugin adds to your inventory as a gather bonus never passes through the gather event, so no objective counts it.
## Paying the same item out
[Section titled “Paying the same item out”](#paying-the-same-item-out)
To reward the custom item, define it once as an [Item custom reward definition](/custom-rewards/#item-definitions). Rewards name the item and skin in separate fields, not with the colon.
# Edit contracts mid-rotation
> Learn what happens when you edit a contract while it is already in an active category rotation and what to expect.
This isn’t as much of a guide as it is information about what to expect when you edit a contract while it is already in an active category rotation. The short answer is: in most cases, this is not a problem and the plugin will handle this gracefully. That being said, there are a couple of things to keep in mind depending on the state of a player’s contract progress when the edit takes effect.
## How edits take effect
[Section titled “How edits take effect”](#how-edits-take-effect)
Edits do not reach live players the instant you save. They apply on the next **data reload** (a plugin reload or server restart). At that point every player’s saved progress on the contract is re-evaluated against the new version and quietly (sometimes loudly) repaired so it stays consistent. Two rules govern that repair:
1. **Claimed contracts are frozen.** Once a player has claimed a contract’s reward, nothing you change can revoke it or ask more of them. If your edit adds requirements, their progress is auto-satisfied to match, so a claimed contract stays claimed.
2. **Unclaimed progress is re-evaluated.** Progress on a contract the player has started but not yet claimed can go up, down, complete, or un-complete to line up with the new objectives and amounts.
3. **Not-yet-accepted contracts are unaffected.** If a player has not accepted the contract yet, there is no saved progress to reconcile, so your edit simply applies.
## What each edit does
[Section titled “What each edit does”](#what-each-edit-does)
The columns are the player’s state on that contract when the reload happens.
| Edit | In progress | Completed (unclaimed) | Claimed |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| **Add objective** | New objective starts at 0 (if amount-based), the player must complete it too, and the contract stays in progress | Reverts to in progress, and the player must complete the new objective before they can claim | New objective is auto-filled to its required amount and completed, so the contract stays claimed |
| **Remove objective** | Its progress is dropped. If it was the last incomplete objective, the contract auto-completes and the player is notified | Its progress is dropped and the contract stays completed | Its progress is dropped and the contract stays claimed |
| **Increase amount** | More to do, still in progress | Reverts to in progress, and the player must reach the new amount | Progress is bumped up to the new amount and stays claimed |
| **Decrease amount** | If the player is already at or above the new amount, the objective auto-completes and may complete the contract | Stays completed | Stays claimed |
Adding an objective or raising an amount can un-complete a finished contract
Adding an objective, or increasing an objective’s required amount, on a contract a player has completed but not yet claimed pulls it back to in progress. They must satisfy the new requirement before they can claim it. Claimed contracts are exempt: their progress is auto-satisfied for them and they keep the reward.
Prefer editing over deleting
When you want an objective gone, consider whether you can change it instead of deleting it. Editing an objective keeps each player’s progress on it. Deleting the objective discards that progress for everyone, and it cannot be recovered if you add the objective back later.
Reload when few players are online
Because edits apply on the next reload, schedule reloads for quiet hours (or when your server restarts). The fewer players are mid-contract, the fewer are affected by any mid-flight adjustment.
# Getting item & entity names
> Learn how to find the correct item and entity names to use in your contracts
A lot of objectives can be configured with item short names or entity names. This guide explains how to find the correct names to use in your contracts.
## Item short names
[Section titled “Item short names”](#item-short-names)
Item short names are arguably the easiest to find. Simply head to [RustHelp’s Item Database](https://rusthelp.com/tools/admin/item-list), search for the item you want and copy the **Short name**. For example, the **Short name** for a [LR-300 Assault Rifle](https://rusthelp.com/items/lr) is `rifle.lr300`.
> The Web editor is pretty good at suggesting item short names as you type. Because Rust exposes all items in the game to plugins with a lot of details about them, I’m able to programmatically generate a full list of all items in the game and suggest them to you in the editor as you type. I just need to re-run the script every now and then so the editor stays up to date with the latest Rust items. If you notice an item is missing, please let me know on Discord!
A custom item made from a base item and a skin is written as `shortname:skin`. [Targeting custom items](/guides/custom-items/) explains where the skin’s workshop ID comes from.
## Entity names
[Section titled “Entity names”](#entity-names)
Unlike item short names, entity names are not as easy to find and are much more numerous. There are a couple of ways to find an entity’s name.
> Similar to item short names, Rust exposes all entities in the game to plugins. However, unlike items, these entities are just string paths to their file. They’re not detailed at all like items are. For this reason, the Web editor *knows* all the entities when you type, but it can’t “pretty” suggest them with display names like it can for items. Some common entities have been manually curated and given display names, but for most other ones, you’ll need to know the exact entity name to use it in your contract. The editor will still suggest them to you, but they will be displayed as their raw prefab name.
Caution
Some entities have more than one entity name. For example, there’s no such thing as just a “scientist”. Scientists have 10+ different entity names (`scientistnpc_heavy`, `scientistnpc_rhib`, `scientistnpc_cargo`, `scientistnpc_ptboat`, `scientistnpc_roamtethered`, …). These are already included as part of the default `@scientists` preset, but other entities may not be (such as road barrels).
### Containers and bodies
[Section titled “Containers and bodies”](#containers-and-bodies)
The [Loot](/contracts/objectives/loot/) objective’s `containers` list takes the same entity names: `supply_drop` for the airdrop, `crate_normal` for a military crate, `loot_barrel_1` for a road barrel. Bodies are their own entity, separate from the NPC that left them. A scientist of any type leaves a `scientist2.corpse` and a scarecrow leaves a `murderer_corpse`, so `@Scientists` in a containers list does nothing. The editor suggests the common containers as you type, and the plugin ships `@Normal Crates`, `@Military Crates`, `@Hackable Crates` and `@Barrels` presets.
### Custom NPC display names
[Section titled “Custom NPC display names”](#custom-npc-display-names)
If you run a custom AI plugin like BetterNpc or NpcSpawn, the [Kill](/contracts/objectives/kill/) and [Damage](/contracts/objectives/damage/) objectives can also match those NPCs by their **display name**. Use the name exactly as it appears in your NPC config, like `Boss Guard`. Casing doesn’t matter. Real player names never match.
### Using Carbon’s Rust Entities Reference
[Section titled “Using Carbon’s Rust Entities Reference”](#using-carbons-rust-entities-reference)
You can search through Carbon’s exported list of Rust entities on their [Rust Entities Reference](https://carbonmod.gg/references/entities/). It lists every entity in Rust, including animals, deployables, and more. Only use the last part of the prefab name, without `.prefab` at the end. E.g:
* `assets/bundled/prefabs/radtown/oil_barrel.prefab` → `oil_barrel`.
* `assets/prefabs/npc/patrol helicopter/patrolhelicopter.prefab` → `patrolhelicopter`.
* `assets/rust.ai/agents/npcplayer/humannpc/scientist/gen2/scientist2.prefab` → `scientist2`.
### Using the in-game console
[Section titled “Using the in-game console”](#using-the-in-game-console)
While looking at the entity in-game, open the console and type `entity.debug_lookat`. Some entities will highlight with a green outline after the command. Use the **Ent** property that gets printed in the info dump. E.g: for a barrel:
```plaintext
Looking At: assets/bundled/prefabs/radtown/loot_barrel_1.prefab
Ent: loot_barrel_1
Pos: (-1076.24, 8.98, 1186.99)
Layer: Default
Hierarchy: assets/bundled/prefabs/radtown/loot_barrel_1.prefab
```
You would use `loot_barrel_1` as the entity name in your contract.
Another way to find entity names is to use the `spawn` command. If you know approximately what you’re looking for, the `spawn` command will suggest entity names that match your search when no entity matches it exactly. For example, if you type `spawn scientist`, the console will suggest all entity names that contain the word “scientist”. You can then use one of those names in your contract.
```plaintext
> spawn scientist
Unknown entity - could be:
sentry.scientist.barge
sentry.scientist.barge.static
sentry.scientist.static
heavyscientistplushie_deployed
scientistplushie_deployed
heavyscientist_youtooz.deployed
scientistboatoilrigmanager
ch47scientists.entity
scientist2.corpse
scientist2.corpse
scientist2.grenade.f1.deployed
scientist2.grenade.smoke.deployed
scientist2.heavy.corpse
scientist2.heavy
scientist2
scientist2.shotgun
scientistnpc_arena
scientistnpc_bradley
scientistnpc_bradley_heavy
scientistnpc_cargo
scientistnpc_cargo_turret_any
scientistnpc_cargo_turret_lr300
scientistnpc_ch47_gunner
scientistnpc_excavator
scientistnpc_full_any
scientistnpc_full_lr300
scientistnpc_full_mp5
scientistnpc_full_pistol
scientistnpc_full_shotgun
scientistnpc_heavy
scientistnpc_junkpile_pistol
scientistnpc_oilrig
scientistnpc_outbreak
scientistnpc_patrol
scientistnpc_patrol_arctic
scientistnpc_peacekeeper
scientistnpc_ptboat
scientistnpc_rhib
scientistnpc_roam
scientistnpc_roam_nvg_variant
scientistnpc_roamtethered
```
# Translating your content
> Give contract titles, descriptions, category names, objective and reward text, and preset display names per-language translations that players see in their own language.
The [localization](/localization/) system covers the plugin’s own messages. This page covers the other half: the content **you** author. Contract titles and descriptions, category names and descriptions, objective and reward text, and preset display names can all carry per-language translations, stored right in your data files.
Default content is English only
All default content is written in English and is not translated on purpose to reduce the file size and maintenance burden for newcomers. If you want to translate the default content without too much effort, you can do so by dumping the data files to an LLM of your choice and asking it to translate them. (point them to this page to explain the translation format and fallback behavior)
## The Translations page
[Section titled “The Translations page”](#the-translations-page)
The [web editor](https://editor.rustcontracts.com) has a **Translations** page in the sidebar that collects the text you have written in one place, so you can see what is missing and fill the gaps without digging through your data files. The sections below explain the JSON it writes and how the fallback works, which you only need to read if you edit the files by hand or feed them to an LLM.
* Fields are grouped by content type (contracts, objectives, rewards, categories, presets, custom rewards, prestige tiers), in the same order as the rest of the editor. Each group shows how many translations are missing. A prestige tier’s rewards are in the rewards group, with the tier’s name in front.
* A field you left empty is not listed, because there is nothing to translate. Most rewards have no title or description of their own, so the rewards group is usually short. Write the title on the reward’s own page and it shows up here.
* Type in the **Filter by name** box to narrow the groups you have open. It matches item and field names, not the text itself.
* The **Missing only** switch is on by default, so the page only shows fields with a missing translation. Turn it off to see the ones you have already done. The list only updates when you change the filter or the switch, so a field you just finished does not jump away while you work.
* Each field shows its authored text next to one input per language. Type the translation and it saves when you leave the input. To change the authored text itself, click the item’s name, it links to the item’s own page.
* **Add language** starts a new language: type a code like `ru` and every field shows an empty input for it. The language becomes part of your config once you save the first translation in it.
On the Lite edition, custom item names on rewards and everything from prestige tiers show the usual premium badge. A custom reward’s unit label can only be translated when its command uses `{amount}`, and the page says so on rewards where it doesn’t.
## How it works
[Section titled “How it works”](#how-it-works)
Every content text field has a matching `*Translations` sibling: `title` has `titleTranslations`, `description` has `descriptionTranslations`, a category’s `name` has `nameTranslations`, and a preset’s `displayName` has `displayNameTranslations`. Each one maps a language code to the translated text, or is `null` when there are no translations:
```json
{
"title": "Big Game Hunter",
"titleTranslations": {
"fr": "Chasse au gros gibier",
"es": "Cazador de caza mayor"
},
"description": "Take down the island's most dangerous predators.",
"descriptionTranslations": null
}
```
When a player opens the menu, each field resolves independently:
1. Look up the player’s language in the field’s translations, matching the language code case-insensitively.
2. If there is no entry for that language, or the entry is blank, show the authored field instead.
There is no region fallback: a player on `zh-CN` gets the `zh-CN` entry or the authored text, never a `zh` entry. The player’s language is the one their framework tracks, the same one used for [message localization](/localization/#how-a-players-language-is-chosen).
## The authored text has no language
[Section titled “The authored text has no language”](#the-authored-text-has-no-language)
The plain field (`title`, `description`, `name`, `displayName`) is not “the English text”. It is the fallback every player without a matching translation sees, in whatever language you wrote it. If you author your content in French, French is the fallback and an `en` entry is just another translation. Write the authored text in the language most of your players speak, then add translations for the rest.
Like message localization, partial coverage is fine. A missing or blank entry falls back per field, so you can translate the contracts that matter first and finish over time.
## Preset display names
[Section titled “Preset display names”](#preset-display-names)
Presets carry a `displayName` that requirement chips show instead of the raw key, and it is the translatable part. The key stays the identifier that `@` references resolve against, so renaming what players see never breaks your contracts. See [Presets](/presets/#shape) for the full shape.
# Leaderboard
> Ranks your server's players by Contract Score, highest first.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
The leaderboard ranks your players by [Contract Score](/configuration/score/), highest first. Players open it from the button in the top right of the contracts menu.
When the [prestige track](/prestige/) is on, each row also shows the player’s current prestige tier next to their score. See [the leaderboard column](/prestige/#the-leaderboard-column) for what that number means and why it can go down.
## Config
[Section titled “Config”](#config)
Both settings live under `UI` > `Leaderboard` (see [UI](/configuration/ui/#leaderboard)).
| Setting | Type | Default | Description |
| ------------------ | ------- | ------- | ------------------------------------------------------------------- |
| `Enabled` | boolean | `true` | When `false`, the header button is not drawn and there is no board. |
| `Players Per Page` | int | `10` | How many players a single page of the board can hold. |
The pager at the bottom only appears when there is more than one page, so a server with eight ranked players sees no pager at all. The same advice as the contract list applies: every row on a page is sent and drawn at once, so keep this to a sane number rather than putting your whole player base on one page, otherwise there may be visual glitches.
## Wipes
[Section titled “Wipes”](#wipes)
The board follows your existing `Score` > `ResetOnWipe` setting. Leave it at its default and the board is a race for the wipe, starting empty on every new map. Set it to `false` and the board keeps counting for the life of the server.
Admins can also clear it at any time with `contracts.wipe_scores`, or move a single player with `contracts.give_score` and `contracts.take_score` (see [Commands](/commands/)).
## Lite
[Section titled “Lite”](#lite)
The board is a Full edition feature. On Lite the header button is not drawn and there is no board, but Score rewards still pay out and `/contracts score` still works, so scores keep counting. Upgrade and the board is already full of them.
# Localization
> Translate the messages Contracts shows players into other languages, or customize the English text, using your framework's language files.
> **TL;DR;** Contracts’ localization system works just like most other plugins that supports it and only comes with the English language file. You can translate it into other languages by copying the English `oxide/lang/en/Contracts.json` or `carbon/lang/en/Contracts.json` file into a new language folder and translating the values. You can also edit the English text itself. See below for the full details.
Every message Contracts shows players goes through your framework’s localization system: chat messages, menu labels, buttons, statuses, condition text and popup text. On first load, the plugin registers its default English messages and the framework writes them to a **language file**. You can translate that file into other languages, or edit the English text itself.
## Language files
[Section titled “Language files”](#language-files)
Alongside `config`, `data` and `logs`, your framework keeps a `lang` folder with one subfolder per language and one JSON file per plugin. The default messages for Contracts live at:
* Oxide
`oxide/lang/en/Contracts.json`
* Carbon
`carbon/lang/en/Contracts.json`
The file is a flat JSON object. Keys identify messages and never change. Values are the text players see:
```json
{
"contracts.commands.score_current": "Your Contract Score: {0}.",
"contracts.messages.contract_completed": "Contract Completed: {0}",
"contracts.ui.menu.accept_contract": "ACCEPT CONTRACT"
}
```
Note
The lite edition registers fewer messages than premium (it has no HUD, for example), so its `en/Contracts.json` contains fewer keys. That’s normal. See [Editions](/editions/) for what each edition includes.
## Adding a language
[Section titled “Adding a language”](#adding-a-language)
1. Find the two-letter code of your language, e.g. `ru` (Russian), `de` (German), `fr` (French), `es` (Spanish).
2. Create the language folder next to `en` if it doesn’t exist yet, and copy the English file into it:
* Oxide
`oxide/lang/en/Contracts.json` → `oxide/lang/ru/Contracts.json`
* Carbon
`carbon/lang/en/Contracts.json` → `carbon/lang/ru/Contracts.json`
3. Translate the **values** in the copied file. Leave the keys untouched.
4. Reload the plugin so the framework picks up your changes:
* Oxide
```text
o.reload Contracts
```
* Carbon
```text
c.reload Contracts
```
Players whose Rust client runs in that language now see the translated messages automatically. Nothing else to configure.
## Translation rules
[Section titled “Translation rules”](#translation-rules)
A few things must survive the translation, or messages will render wrong:
* **Never translate the keys.** The plugin looks messages up by key, so a translated key is a missing message and players would see the raw key instead.
* **Keep the `{0}`, `{1}`, … placeholders.** They are replaced at runtime with values like contract names or amounts. You can move a placeholder anywhere in the sentence to fit your language’s grammar, but if you delete one (or add one that doesn’t exist), the message breaks and falls back to English.
* **Keep the `...` tags.** These are Unity rich text tags that highlight the important part of a message. Move them so they wrap the same words in your translation. You can also restyle them deliberately, as long as every opening tag keeps its closing tag.
* **Keep the JSON valid.** Escape double quotes inside messages (`\"`) and keep the commas between entries. If the file fails to parse, the whole language falls back to English.
## How a player’s language is chosen
[Section titled “How a player’s language is chosen”](#how-a-players-language-is-chosen)
You don’t assign languages to players. The framework resolves each message per player:
1. **The player’s language.** Both Oxide and Carbon detect it automatically from the player’s Rust client language and keep it in sync when the player changes it. On Oxide, players can also override it manually with the `/lang` chat command (e.g. `/lang ru`).
2. **The server’s language** if the player’s language has no file. This is also what the server console uses. It defaults to English. Change it with the `lang ` console command on Oxide, or the `Language` setting in `carbon/config.json` on Carbon.
3. **English** as the final fallback, per message: any key missing from a translated file shows its English text.
Because untranslated messages simply fall back to English, a partial translation is perfectly fine to ship on your server and finish over time.
## After a plugin update
[Section titled “After a plugin update”](#after-a-plugin-update)
Plugin updates sometimes add new messages or remove old ones. The English file is kept in sync automatically on load: new keys are added, removed keys are cleaned up, and values you’ve customized are preserved. Translated files are synced too, but the new keys arrive with their **English** text (on Oxide they’re written into your file the first time the language is used, on Carbon they just fall back at runtime).
After updating the plugin, skim your translated file for English strings, or diff it against `en/Contracts.json`, and translate whatever is new.
## Customizing the English text
[Section titled “Customizing the English text”](#customizing-the-english-text)
Localization isn’t only for other languages. You can edit the values in `en/Contracts.json` to reword any message on your server, then reload the plugin. Your customized values survive plugin updates. Only added and removed keys change.
## What localization does not cover
[Section titled “What localization does not cover”](#what-localization-does-not-cover)
Language files cover the plugin’s own messages, not your content or the game’s names:
* **Contract, category and preset content** (names, descriptions) lives in your [data files](/getting-started/default-data/) and has its own translation system: every text field carries a `*Translations` sibling with per-language overrides, and the web editor’s Translations page fills them in from one place. See [Translating your content](/guides/translating-your-content/).
* **Item names** shown in objectives and rewards come from the game’s item definitions.
* **Entity and monument display names** are curated by the plugin and currently English-only.
# Presets
> Reusable named lists of item and entity shortnames, referenced as @name from contracts.
A **preset** is a named, reusable list of Rust item or entity shortnames. Define a list once (say, every scientist type) and reference it as `@presetName` from any objective or condition field that takes an item or entity list, instead of repeating the whole list in every contract.
Presets are stored in the `preset_data.json` data file and authored with the [web editor](https://editor.rustcontracts.com).
## Shape
[Section titled “Shape”](#shape)
A preset has a display name, a list of one or more shortnames and an optional icon, stored under a key you choose:
```json
{
"presets": {
"Scientists": {
"displayName": "Scientists",
"displayNameTranslations": null,
"values": [
"scientist2",
"scientist2.heavy",
"scientistnpc_oilrig",
"scientistnpc_patrol"
],
"icon": null,
"iconColor": null,
"iconRaw": false
},
"Ballistas": {
"displayName": "Ballistas",
"displayNameTranslations": null,
"values": [
"ballista.static",
"ballista.mounted"
],
"icon": null,
"iconColor": null,
"iconRaw": false
}
},
"version": {
"Major": 1,
"Minor": 16,
"Patch": 1
}
}
```
The key is the identifier that `@` references resolve against. `displayName` is the text players see, and `displayNameTranslations` holds optional per-language overrides for it (see [Translating your content](/guides/translating-your-content/)). The plugin ships with a ready-made `@Scientists` preset (every scientist NPC type) plus a handful of others.
## Fields
[Section titled “Fields”](#fields)
| Field | Type | Default | Description |
| ------------------------- | --------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `displayName` | string | | Label shown wherever the preset is referenced, like requirement chips. |
| `displayNameTranslations` | object | `null` | Per-language overrides for `displayName`. `null` when there are none. |
| `values` | array of string | | The item or entity names the preset stands for. An item name takes the same `shortname:skin` form as an objective’s items list. |
| `icon` | string | `null` | Direct image URL for the icon on this preset’s chips. `null` keeps the default icon. |
| `iconColor` | string | `null` | Color for the icon, as hex (`#rrggbb` or `#rrggbbaa`), `rgb()` or `rgba()`. `null` keeps the color. |
| `iconRaw` | boolean | `false` | Set `true` when the icon is a full-color image (a logo, custom art) so it renders in its own colors. |
## Referencing a preset
[Section titled “Referencing a preset”](#referencing-a-preset)
Anywhere a field takes an item or entity list (`entities`, `items`, `containers`, `item`, `fish`, `bait`, weapon lists, and so on), you can write `@presetName` in place of a literal shortname. At load time the plugin expands the reference into that preset’s list:
```json
{
"type": "Kill",
"title": "Hunt scientists",
"amountRequired": 10,
"conditions": {},
"entities": ["@scientists"]
}
```
You can mix preset references and literal shortnames in the same list, for example `["@scientists", "scarecrow"]`.
## Naming a custom item
[Section titled “Naming a custom item”](#naming-a-custom-item)
A custom item (a base item with a skin) is written as `shortname:skin`, and a preset is the best place to keep it. With one skinned value and a `displayName`, every chip that references the preset shows your item’s name instead of the base item’s. See [Targeting custom items](/guides/custom-items/) for the walkthrough.
## Nesting
[Section titled “Nesting”](#nesting)
A preset can reference **other presets**, so you can compose larger lists from smaller ones:
```json
{
"presets": {
"Weak NPCs": {
"displayName": "Weak NPCs",
"displayNameTranslations": null,
"values": ["scarecrow", "murderer"]
},
"All NPCs": {
"displayName": "All NPCs",
"displayNameTranslations": null,
"values": ["@Scientists", "@Weak NPCs"]
}
}
}
```
References are expanded recursively at runtime.
See the [Objectives overview](/contracts/objectives/#item-and-entity-lists) for more on shortnames and how item and entity lists are matched.
# Prestige
> A finite track of prestige tiers your players climb with Contract Score and claim rewards from.
[⭐ Premium](/editions/ "Premium feature (see Editions)")
As a player’s [Contract Score](/configuration/score/) grows they reach **prestige tiers** and each tier pays rewards they claim from the prestige view in the menu.
You write the track yourself, so it has exactly the tiers you define and it ends at the last one. Tiers are stored in the `prestige_data.json` data file and authored with the [web editor](https://editor.rustcontracts.com).
By default, the prestige track is enabled, but no tiers are defined, essentially making it inactive until you add them.
## Prestige track
[Section titled “Prestige track”](#prestige-track)
```json
{
"tiers": [
{
"threshold": 500,
"title": null,
"titleTranslations": null,
"description": null,
"descriptionTranslations": null,
"rewards": {
"tier_500_scrap": {
"type": "Item",
"title": "",
"description": "",
"eligiblePermissions": [],
"hidden": false,
"item": "scrap",
"quantity": 500
}
}
},
{
"threshold": 2000,
"title": "Veteran",
"titleTranslations": {
"es": "Veterano"
},
"description": "For the players who stuck around all wipe.",
"descriptionTranslations": {
"es": "Para los jugadores que se quedaron toda la wipe."
},
"rewards": {
"tier_2000_cash": {
"type": "Economics",
"title": "",
"description": "",
"eligiblePermissions": [],
"hidden": false,
"amount": 1000
}
}
},
{
"threshold": 5000,
"title": null,
"titleTranslations": null,
"description": null,
"descriptionTranslations": null,
"rewards": {}
}
],
"version": {
"Major": 1,
"Minor": 16,
"Patch": 1
}
}
```
| Field | Type | Description |
| -------------------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `threshold` | integer (min 1) | The total Contract Score a player needs to reach this tier. Must be above 0 and different from every other tier. Tiers are listed from the lowest threshold to the highest. |
| `title` | string \| null | Optional name shown in the prestige view next to the tier number. null shows the number alone. The leaderboard never shows it. |
| `titleTranslations` | map \| null | Per-language overrides for title, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see title instead. null when there are none. |
| `description` | string \| null | Optional text shown in the details panel when a player clicks the tier in the prestige view. null shows no description. |
| `descriptionTranslations` | map \| null | Per-language overrides for description, mapping a language code (like "fr" or "zh-CN") to the translated text. Players whose language has no entry (matched case-insensitively, no region fallback) or a blank one see description instead. null when there are none. |
| [`rewards`](/contracts/rewards/) | map | What the player gets when they claim the tier, in the same shape as a contract's rewards. Every reward type works except Score. A tier with no rewards still counts toward the player's tier but has nothing to claim. |
## Rules and Validations
[Section titled “Rules and Validations”](#rules-and-validations)
* A tier’s number is its place in the list, starting at 1. There is no id and no key to keep track of.
* `threshold` is the total CS a player needs to reach the tier. It must be above 0 and different from every other threshold in the track. The threshold defines the tier’s position on the track.
* Keep the list from the lowest threshold to the highest. If it is out of order, the plugin sorts it at load, warns in the console and saves the sorted file back. The editor already takes care of this for you.
* Every reward type works except Score. CS is what the track runs on, so a tier paying CS would push players further up the track for claiming it. A Score reward inside a tier is dropped at load with a console warning.
* A tier with no rewards is still valid. Players reach it and it moves their tier number, there is just nothing for them to claim. The third tier above is one of those.
* An empty `tiers` list is a valid file. The track then has nothing to show and nothing to pay and the menu button stays hidden.
## What players see
[Section titled “What players see”](#what-players-see)
A star button appears in the menu header. It is only shown when prestige is on and the track has at least one tier and it turns your accent color while the player has a tier waiting to be claimed.
A tier row is in one of four states:
* **Claimable**: the player has the score and has not taken the rewards yet. The row is highlighted in your accent color and the state cell is a **Claim** button. With [`Claim Tiers In Order`](#claiming-in-order) on, only the lowest claimable tier gets the highlight and a working **Claim** button. Higher claimable tiers show a greyed out **Claim** until the ones below are taken.
* **Claimed**: the rewards are already taken.
* **Reached**: the player has the score but there is nothing to claim, either because the tier has no rewards or because none of them are for this player.
* **Locked**: the player is short of the threshold. The cell reads how much more CS they need and the rewards are still drawn so they know what they are chasing.
Clicking a tier row shows that tier in the details panel on the right: its description, each reward with its amount, and a **Claim** button when the tier is claimable.
## Claiming in order
[Section titled “Claiming in order”](#claiming-in-order)
By default, a player can claim their claimable tiers in any order. That breaks tier rewards that run commands in a set order. The common case is a chat tag group per tier: each tier removes the lower tier groups and adds its own. A player who claims tier 3 and then tier 2 ends up in both groups and shows both tags in chat.
Turn on `Prestige` > `Claim Tiers In Order` to make players claim their lowest claimable tier first. A tier with no reward for the player is skipped, so it never blocks the tiers above it. **Claim All** follows the same order.
Tier 3 of a chat tag track could look like this:
```json
{
"threshold": 5000,
"title": "Veteran",
"titleTranslations": null,
"rewards": {
"tier_3_tag": {
"type": "Command",
"title": "Veteran chat tag",
"description": "",
"eligiblePermissions": [],
"hidden": false,
"commands": [
"oxide.usergroup remove {playerId} prestige1",
"oxide.usergroup remove {playerId} prestige2",
"oxide.usergroup add {playerId} prestige3"
],
"reverseCommands": [],
"icon": null,
"iconColor": null,
"iconRaw": false
}
}
}
```
Turn it on at wipe
If you’re turning the setting on, adding tiers or changing thresholds, I strongly recommend doing so at wipe. Mid-wipe, a player can be left with a lower tier to claim after a higher one, breaking the intended order of claims.
## The leaderboard column
[Section titled “The leaderboard column”](#the-leaderboard-column)
With the track on, the [leaderboard](/leaderboard/) gains a **Tier** column next to each player’s score, showing their current tier number (a dash for players who have not reached tier 1 yet).
That number is worked out from the score every time the board is drawn: it is the count of tiers the player’s CS has reached.
## Losing and regaining score
[Section titled “Losing and regaining score”](#losing-and-regaining-score)
`contracts.take_score`, a reward reversal or a wipe all lower a player’s current tier, because the number follows the score.
Claimed tiers stay claimed. A tier a player already took stays taken even if their score falls back under it, so you never pay the same tier twice. A tier they reached but never claimed goes back to **Locked** and waits there until their score comes back.
Editing the track mid-wipe
Adding or removing a tier leaves everyone’s existing claims correct, because a claim is recorded against the **threshold**, not the position of the tier. However, changing a threshold makes that tier a new one as far as the plugin is concerned, so **it becomes claimable again** once players reach the new number. Be mindful of this when editing the track and consider only editing existing tiers at the next wipe instead. With `Claim Tiers In Order` on, these edits can also make players claim tiers out of order (see [Claiming in order](#claiming-in-order)).
## Locking contracts and categories
[Section titled “Locking contracts and categories”](#locking-contracts-and-categories)
A contract or a category can carry a `minPrestigeTier`, which is the lowest tier a player needs before they can accept it. A category’s tier is a floor for every contract in it and when both set one, the higher of the two applies. A locked contract stays in rotation and stays in the list.
**Edge cases:**
* The lock is an accept gate and nothing more. A player who drops below the tier keeps every contract they already hold, including the progress on it and the reward at the end.
* A tier above the length of your track is still enforced, so nobody can accept that contract. The plugin prints a console warning at load for each one, so a typo or a deleted tier is easy to spot. The editor also has mechanisms to prevent this from happening, so you don’t have to worry about it as much.
* With `Prestige` > `Enabled` off, every tier lock is ignored and the contracts accept normally. The plugin prints one console warning at load listing what it let through.
The field sits on both the [contract](/contracts/) and the [category](/categories/).
## Config
[Section titled “Config”](#config)
`Prestige` > `Enabled` turns the whole track on or off (see [Prestige](/configuration/prestige/)):
| Field | Type | Default | Description |
| ---------------------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Enabled` | boolean | `true` | Turns the prestige track on. Off hides the prestige view and the leaderboard tier column, skips the tier messages, and ignores every tier lock. The prestige data file still loads. |
| `Claim Tiers In Order` | boolean | `false` | Players have to claim their lowest claimable tier first. Higher tiers show a greyed out Claim button until the ones below are taken. Turn it on when tier rewards run commands that depend on order, like a chat tag group per tier. |
`UI` > `Prestige` holds the view’s page size (see [UI](/configuration/ui/#prestige)):
| Field | Type | Default | Description |
| ---------------- | --------------- | ------- | ---------------------------------------------------- |
| `Tiers Per Page` | integer (min 1) | `25` | How many tiers each page of the prestige view lists. |
The star button itself takes an icon override under `UI` > `Icons` > `Ui` > `Prestige`, like every other icon the plugin ships.
## Wipes
[Section titled “Wipes”](#wipes)
The track follows your existing `Score` > `ResetOnWipe` setting, because tiers are made of score. There is no separate prestige wipe setting. If you wish to keep prestige tiers across wipes, you should set `Score` > `ResetOnWipe` to `false`.
Admins can clear both at any time with `contracts.wipe_scores`, which wipes claimed tiers along with the scores (see [Commands](/commands/)).
## What prestige is not
[Section titled “What prestige is not”](#what-prestige-is-not)
> TL;DR; The prestige system is deliberately designed **NOT** to become a leveling or progression system, so it doesn’t overshadow the plugin’s core vision, which is focused on questing.
I want Contracts’ purpose to remain focused on the unique questing experience it delivers to your players. The prestige system was almost abandonned because I was concerned it would shift the focus away from questing and into a leveling/progression system.
Instead, I deliberately designed the prestige system to complement the existing questing experience rather than creating new mechanics to define what “prestige” means on Contracts. It’s essentially one big long-term quest. This is done by:
* piggybacking on contract/category lock mechanics that already existed
* reusing existing rewards
* making use of CS instead of using another plugin (e.g. Skill Tree) to track progression, giving CS another purpose beyond the leaderboard.
# Privacy
> TLDR: no cookies, no tracking ID, nothing tied to you.
This site and the editor collect anonymous usage analytics through PostHog, hosted in the EU and running in cookieless mode. Analytics set no cookies and put no ID in your browser. Instead of an ID, PostHog counts visitors with a server-side hash that changes every day, and your IP address is never kept.
What gets counted:
* Pages viewed
* Editor feature usage
* Docs searches
* Copy and outbound link clicks
* Your browser, operating system, screen size and time zone
The editor does save your work in your browser’s local storage, and the docs site saves the Lite and Full toggle the same way. That never leaves your machine and is not part of analytics. Clearing site data in your browser deletes it.
None of it is tied to you, and nothing is sold or shared, so there is nothing to request or delete.
Questions? Ask on [Discord](https://discord.rustcontracts.com).
# Roadmap
> Follow the development progress of Contracts and see what is coming next!
Follow the development progress of Contracts and see what is coming next! This is the Notion board I use to track development and stay transparent with the community.
## Got feedback or a feature request?
[Section titled “Got feedback or a feature request?”](#got-feedback-or-a-feature-request)
The best and easiest way to get your feedback or feature request on this board is to open a post in the **#👍・feedback** channel of the [Discord server](https://discord.rustcontracts.com)!
## Roadmap
[Section titled “Roadmap”](#roadmap)
> [View in fullscreen](https://roadmap.rustcontracts.com/)
Legend
* **Backlog**: Tasks that are planned but not yet started.
* **In Progress**: Currently being worked on. Note that this doesn’t mean I’m actively working on it at this moment, as I have other projects than Contracts. However, when I’m working on Contracts, these are the tasks I’m focusing on.
* **To Release**: Tasks that have have their development completed but are not yet in the official release. These may be part of a larger release or are in testing on my PVE server: [Survivor’s Cove](https://www.survivorscove.com/).
* **Released**: Tasks that have been released in an official version of the plugin.
* **Won’t Do**: Tasks that were once planned but have been dropped for various reasons. They may not fit the vision of the plugin, too complex for the value they provide or are no longer relevant. They may be revisited in the future, but I wouldn’t count on it.
[Contracts public roadmap](https://rustcontracts.notion.site/ebd/3541c1211e658028b04eea0552ae2fce)