Docs: - .env.example: documents all environment variables with defaults - CONTRIBUTING.md: dev setup, running tests, code style, design constraints - MODDING.md: full schema reference for cards, packs, objectives, and SBCs - .gitea/workflows/ci.yml: Gitea Actions CI (fmt check, clippy, build, test) Middleware (#50-52): - Body limit (256 KB) via DefaultBodyLimit on all routes (#50) - Concurrency limit (256 concurrent requests) via semaphore middleware; returns 429 Too Many Requests when at capacity (#51) - Correlation IDs via tower_http request_id layers; sets x-request-id UUID on every request and propagates it to the response headers (#52) Layer order (outermost → innermost): CorsLayer → DefaultBodyLimit → concurrency limit → SetRequestId → TraceLayer → PropagateRequestId → routes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5.1 KiB
OpenFUT Modding Guide
All game content is driven by JSON files in the data/ directory. You can add
new cards, packs, objectives, and SBCs without touching any Rust code.
The server reloads data files on startup. After changing a file, restart
openfut-core to pick up the changes.
Cards — data/cards/*.json
Each file contains a JSON array of CardDefinition objects. All files in the
directory are loaded at startup.
[
{
"id": "card_unique_id",
"name": "Player Name",
"overall": 82,
"position": "ST",
"nation": "Brazil",
"league": "Brasileirao",
"club": "Flamengo",
"pace": 88,
"shooting": 85,
"passing": 74,
"dribbling": 82,
"defending": 38,
"physical": 76,
"rarity": "gold",
"image_path": null
}
]
Valid rarity values: bronze, silver, gold, raregold, totw, hero, icon
Valid positions: GK, CB, LB, RB, CDM, CM, CAM, LM, RM,
LW, RW, ST, CF
idmust be unique across all card files.overallis 1–99.- Attribute values are 1–99.
image_pathcan be a relative path to an image ornull.
Packs — data/packs/*.json
Each file contains a JSON array of PackDefinition objects.
[
{
"id": "my_custom_pack",
"name": "Custom Pack",
"description": "12 random gold players.",
"cost_coins": 7500,
"slots": [
{
"count": 12,
"min_overall": 75,
"rarity_filter": ["gold", "raregold"],
"guaranteed_rare": false
}
]
}
]
Each slot defines one group of cards to draw:
count— how many cards to draw for this slot.min_overall— minimum overall rating (inclusive), ornullfor no filter.rarity_filter— array of rarity strings to draw from, ornullfor any rarity.guaranteed_rare— iftrue, this slot's cards are marked as rare.
A pack can have multiple slots (e.g., one slot of 11 gold cards + one guaranteed rare slot of 1 card).
Objectives — data/objectives/*.json
Each file contains a JSON array of ObjectiveDefinition objects.
[
{
"id": "daily_score_3",
"title": "Hat-Trick Hero",
"description": "Score 3 goals in one session.",
"objective_type": "daily",
"metric": "goalsscored",
"target": 3,
"reward_coins": 500,
"reward_xp": 200,
"reward_pack_id": null
}
]
Valid objective_type values: daily, weekly, milestone, lifetime, season
Valid metric values:
| Metric | Incremented by |
|---|---|
matcheswon |
Winning a match |
matchesplayed |
Playing any match |
goalsscored |
Goals scored in a match |
packsopened |
Opening a pack |
sbcscompleted |
Completing an SBC |
coinsearned |
Coins awarded after a match |
reward_pack_idmust match a packidfromdata/packs/or benull.- Daily objectives are auto-reset to 0 at midnight UTC.
SBCs — data/sbcs/*.json
Each file contains a JSON array of SbcDefinition objects.
[
{
"id": "my_sbc",
"name": "Custom SBC",
"description": "Submit 5 gold players for a reward.",
"expires_at": null,
"requirements": {
"squad_size": 5,
"min_overall": 75,
"max_overall": null,
"required_leagues": ["Premier League"],
"required_nations": [],
"required_clubs": [],
"min_players_from_same_league": null,
"min_players_from_same_nation": null,
"min_players_from_same_club": null
},
"reward": {
"coins": 1000,
"xp": 300,
"pack_id": null
}
}
]
Requirements:
squad_size— exact number of cards to submit (required).min_overall— average overall must meet or exceed this (optional).max_overall— no individual card may exceed this (optional).required_leagues— at least one card from each listed league.required_nations— at least one card from each listed nation.required_clubs— at least one card from each listed club.min_players_from_same_league— max count of any single league ≥ this.min_players_from_same_nation— max count of any single nation ≥ this.min_players_from_same_club— max count of any single club ≥ this.
Cards submitted to an SBC are permanently consumed from the club's collection.
Tips
- Use unique
idvalues that won't collide with future official cards. A good convention iscard_<nation>_<number>, e.g.card_br_042. - You can have multiple JSON files per directory — they are all merged at load time.
- Invalid JSON will prevent the server from starting; check the log output.
- Cards referenced by packs must exist in
data/cards/or they will not be drawn.