Files
OpenFUT-Core/MODDING.md
funman300 f6dd41fd41
CI / Build, lint & test (push) Successful in 2m21s
feat: Phase 4 — dev experience complete
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>
2026-06-25 15:53:28 -07:00

175 lines
5.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
```json
[
{
"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`
- `id` must be unique across all card files.
- `overall` is 199.
- Attribute values are 199.
- `image_path` can be a relative path to an image or `null`.
---
## Packs — `data/packs/*.json`
Each file contains a JSON array of `PackDefinition` objects.
```json
[
{
"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), or `null` for no filter.
- `rarity_filter` — array of rarity strings to draw from, or `null` for any rarity.
- `guaranteed_rare` — if `true`, 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.
```json
[
{
"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_id` must match a pack `id` from `data/packs/` or be `null`.
- Daily objectives are auto-reset to 0 at midnight UTC.
---
## SBCs — `data/sbcs/*.json`
Each file contains a JSON array of `SbcDefinition` objects.
```json
[
{
"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 `id` values that won't collide with future official cards.
A good convention is `card_<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.