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>
This commit is contained in:
+174
@@ -0,0 +1,174 @@
|
||||
# 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 1–99.
|
||||
- Attribute values are 1–99.
|
||||
- `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.
|
||||
Reference in New Issue
Block a user