Files
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

5.1 KiB
Raw Permalink Blame History

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

  • 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.

[
  {
    "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.

[
  {
    "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.

[
  {
    "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.