Bang Average Football logo

Custom Content & Steam Workshop

Guides for Bang Average Football


Creating Custom Databases


Custom databases of teams and players can be created and edited in-game using the built-in database editing features. Alternatievly, for users familiar with JSON, databases can be provided as database.json files.

{
    "display_name": "My Example Database",
    "players": [
        ...
    ],
    "teams": [
        ...
    ]
}

This page describes the expected JSON schema for custom databases. The game will validate custom databases before loading them, and will report any errors preventing the database from being used.


Where are my databases?

The local folder where the game will load custom databases from depends on your OS*.

Windows

C:\Users\<YOUR_WINDOWS_USERNAME>\AppData\Roaming\BangAverageFootball\custom\databases\

macOS

~/Library/Application Support/BangAverageFootball/custom/databases/

Linux

~/.local/share/BangAverageFootball/custom/databases/
The data folder may be in a different location depending on your distro.

*Databases installed from Steam Workshop are installed in a separate location determined by Steam.

Every valid database consists of exactly one database.json file in a unique folder placed directly in the custom databases folder listed above. For example, let's look at the below directory structure:

BangAverageFootball/
    custom/
        databases/
            MyValidDatabase/
                database.json
            MyInvalidDatabase1/
                wip.json
            MyInvalidDatabase1.json

There is only one valid database here: MyValidDatabase. It's the only database correctly formatted as a database.json file within a subfolder of the custom databases folder. The others are not valid and will be ignored by the game.

You can have a look at an example of a small valid database in the _help/ folder in the custom databases directory. Copying the entire ExampleDatabase folder into the custom databases directory will make it visible as a custom database in the game.


Uploading to Steam Workshop

Databases can be shared with others by uploading them to the Steam Workshop, as long as they:

  • Are valid, with no warnings or errors.
  • Include a preview.png file at least 512px x 512px.

Databases can be uploaded to Steam Workshop directly from the database management screen; no additional software is needed:

Screenshot of the database management screen in Bang Average Football.

1. From the main menu, go to More... Custom Content Manage Databases.

Screenshot of a settings modal for a custom database in Bang Average Football.

2. Select the database you wish to upload and select Upload to Steam Workshop.

Screenshot of the 'Upload Item to Steam Workshop' modal in Bang Average Football.

3. Enter a useful, user-discoverable title for your new Steam Workshop item and select Upload to Steam Workshop.

Screenshot of the 'Uploaded to Steam Workshop!' modal in Bang Average Football.

4. That's it! View your new Steam Workshop page and make sure to fill out a description before making it visible!


Object Schemas

Databases consist of two types of objects: players and teams. Each object type has its own schema which must be strictly adhered to; the game will reject database which deviate from this schema!

Player Schema

JSON Examples

{
    "id": "EXAMPLE_PLAYER_10",
    "team_id": "EXAMPLE_TEAM_1",
    "shirt_number": 10,
    "info": {
        "first_name": "Eddie",
        "last_name": "Example",
        "skin": 5,
        "hair": "BROWN",
        "hairstyle": "SHORT",
        "facial_hair": "GOATEE"
    },
    "positions": ["CM", "SC"],
    "stats": {
        "speed": 8,
        "control" 8,
        "passing": 7,
        "shooting": 9,
        "power": 7,
        "tackling": 3,
        "handling": 1,
        "agility": 7
    }
}
{
    "id": "KELLY_SMITH_GK",
    "team_id": "WOODFORD_ROVERS",
    "shirt_number": 1,
    "info": {
        "first_name": "Kelly",
        "last_name": "Smith",
        "skin": 1,
        "hair": "BLACK",
        "hairstyle": "TIGHT_BUN",
        "facial_hair": "NONE"
    },
    "positions": ["GK"],
    "stats": {
        "speed": 4,
        "control" 4,
        "passing": 7,
        "shooting": 3,
        "power": 8,
        "tackling": 2,
        "handling": 8,
        "agility": 7
    }
}

Object Fields

id - string

A unique identifier for this player. Any string can be used as an id (e.g. "731", "JANE_DOE", "HJ8DM3B" etc.), but every player's id must be unique.

team_id - string

The id for this player's team. Every player can have at most 1 team. If team_id is null, this player will be considered a Free Agent.

shirt_number - integer

The shirt/squad number associated with this player. If this player's team_id is null, then shirt_number can also be null.

info - object

This player's name and appearance data.

first_name, last_name - string (max 20 chars each)

The first and last names for this player. A player can have a first name, a last name, or both, but not neither. If only one name is provided, the game will assume this player is mononymous (e.g. "Rivaldo", "Moses", "Cher").

skin - integer

This player's skin tone, ranging from 1 (darkest) to 5 (palest).

hair - string

This player's hair colour; see Hair Colors.

hairstyle - string

This player's hairstyle; see Hairstyles.

facial_hair - string

This player's facial hair; see Facial Hair.

positions - array of string (see Positions)

This player's on-field position(s). Each player can have up to 3 positions, and must have at least 1.

stats - object

This player's in-game stats. Each stat can range from 1-10 inclusive, and any combination of stats is permitted, no matter how silly.

Note that the player's average rating is automatically calculated by the game when the database is loaded, and is not provided by the database itself.

speed, control, passing, shooting, power, tackling, handling, agility - integer

Each individual stat must be assigned a separate integer value.


Team Schema

JSON Examples

{
    "id": "EXAMPLE_TEAM_1",
    "name": "Example United FC",
    "short_name": "Example Utd",
    "first_xi": [
        "EXAMPLE_PLAYER_1",
        "EXAMPLE_PLAYER_2",
        ...
        "EXAMPLE_PLAYER_10",
        "EXAMPLE_PLAYER_11"
    ],
    "formation": "4-4-2",
    "kits": {
        "home": {
            "style": "STRIPES",
            "shirt_primary": "BLUE_ROYAL",
            "shirt_secondary": "WHITE",
            "shorts": "BLUE_ROYAL",
            "socks": "WHITE"
        },
        "away": {
            "style": "SOLID",
            "shirt_primary": "YELLOW",
            "shirt_secondary": "BLACK",
            "shorts": "BLACK",
            "socks": "YELLOW"
        }
    }
}
{
    "id": "WOODFORD_ROVERS",
    "name": "Woodford Rovers FC",
    "short_name": "Woodford",
    "first_xi": [
        "KELLY_SMITH_GK",
        "VERONICA_MONTEIRO_CB",
        ...
        "CLARA_PEDERSEN_LM",
        "NICOLA_JENKINS_SC"
    ],
    "formation": [
        [ [ 2, 1 ], [ 2, 0 ] ],
        [ [ 4, 0 ], [ 4, 0 ] ],
        [ [ 6, 1 ], [ 6, 0 ] ],
        [ [ 1, 3 ], [ 1, 2 ] ],
        [ [ 7, 3 ], [ 7, 2 ] ],
        [ [ 3, 5 ], [ 3, 4 ] ],
        [ [ 5, 5 ], [ 5, 4 ] ],
        [ [ 2, 8 ], [ 2, 7 ] ],
        [ [ 6, 8 ], [ 6, 7 ] ],
        [ [ 4, 10 ], [ 4, 9 ] ]
    ],
    "kits": {
        "home": {
            "style": "HOOPS",
            "shirt_primary": "RED",
            "shirt_secondary": "BLACK",
            "shorts": "WHITE",
            "socks": "BLACK"
        },
        "away": {
            "style": "SOLID",
            "shirt_primary": "BLUE_LIGHT",
            "shirt_secondary": "BLUE_LIGHT",
            "shorts": "BLACK",
            "socks": "BLUE_LIGHT"
        }
    }
}

Object Fields

id - string

A unique identifier for this team. Any string can be used as an id (e.g. "472", "EXAMPLE_UNITED", "P9IA12C" etc.), but every team's id must be unique.

name - string (max length 23)

The full, unshortened name for this team (e.g. "Example United", "AFC Farnsby City" etc.).

short_name - string (max length 15)

A shortened version of this team's name, more appropriate for informal reference or narrower UIs (e.g. "Example Utd", "Farnsby" etc.).

first_xi - array of string (see Player id)

The IDs of the 11 players who will start matches for this team by default, including one goalkeeper. Players are ordered back-to-front, right-to-left.

formation - string or array of array (see Formation Nodes)

This team's default starting formation. This can still be changed in-game when using the team. A string preset can be used (see Formation Presets) or a custom formation can be defined by describing each 'node' which will be occupied by an on-field player. Note that if defining a custom formation:

  • The goalkeeper is never included, and the formation should consist of exactly 10 nodes.
  • Nodes are ordered back-to-front, right-to-left; the first node is the player closest to the goalkeeper (presumably a defender), and the last node is the player furthest upfield (presumably a striker).

kits - object

This team's kits/strips/uniforms.

home, away - object

Each team must have two kits: a home kit and an away kit. For gameplay reasons, it is recommended that the away kit is significantly different visually from the home kit.

style - string

One of "SOLID", "STRIPES" or "HOOPS". Each kit style will appear differently in-game.

"SOLID"

SOLID kit style

"STRIPES"

STRIPES kit style

"HOOPS"

HOOPS kit style

Note that a plain/single-colour shirt can be achieved by making shirt_primary and shirt_secondary the same for any style.

shirt_primary, shirt_secondary, shorts, socks - string

The colour for each kit component. Kits can use any combination of colours, no matter how gaudy. See Kit Colors.


Other Data Types

Kit Colors

"BLACK"
"BLUE_DARK"
"BLUE_LIGHT"
"BLUE_ROYAL"
"BLUE_SLATE"
"BLUE_SLATE_DARK"
"BROWN"
"BROWN_LIGHT"
"GREEN_DARK"
"GREEN_FOREST"
"GREEN_KIWI"
"GREEN_LIGHT"
"GREEN_PINE"
"GREY_DARK"
"GREY_LIGHT"
"LAVENDER"
"LAVENDER_LIGHT"
"MAROON"
"ORANGE_DARK"
"ORANGE_LIGHT"
"PINK_DARK"
"PINK_LIGHT"
"PURPLE"
"PURPLE_DARK"
"PURPLE_LIGHT"
"RED"
"TURQUOISE_LIGHT"
"WHITE"
"YELLOW"

Hair Colors

"BROWN_LIGHT"
"BROWN"
"BROWN_DARK"
"BLACK"
"GREY"
"BLOND"
"GINGER"
"GREEN"
"BLUE"
"RED"

Hairstyles

"BALD"

BALD appearance style

"BALDING"

BALDING appearance style

"AFRO"

AFRO appearance style

"AFRO_SHORT"

AFRO_SHORT appearance style

"BOB"

BOB appearance style

"BRAID_PONYTAIL"

BRAID_PONYTAIL appearance style

"BRAIDS"

BRAIDS appearance style

"BUN"

BUN appearance style

"BUZZ"

BUZZ appearance style

"FANCY"

FANCY appearance style

"FLATTOP"

FLATTOP appearance style

"LONG"

LONG appearance style

"MESSY"

MESSY appearance style

"MOHAWK"

MOHAWK appearance style

"PONYTAIL"

PONYTAIL appearance style

"QUIFF"

QUIFF appearance style

"SHORT"

SHORT appearance style

"SHOULDER_LENGTH"

SHOULDER_LENGTH appearance style

"TIGHT_BUN"

TIGHT_BUN appearance style

Facial Hair

"NONE"

NONE appearance style

"ANCHOR"

ANCHOR appearance style

"CHINSTRAP"

CHINSTRAP appearance style

"CIRCLE"

CIRCLE appearance style

"FULL"

FULL appearance style

"GOATEE"

GOATEE appearance style

"MOUSTACHE_BIG"

MOUSTACHE_BIG appearance style

"MOUSTACHE_SMALL"

MOUSTACHE_SMALL appearance style

"MUTTON_CHOPS"

MUTTON_CHOPS appearance style

"SHADOW"

SHADOW appearance style

"STUBBLE"

STUBBLE appearance style

Positions

Each player can have up to 3 positions, and must have at least 1. There are no restrictions on position combinations, no matter how silly; you're very welcome to have a GK/RB/LM maverick if you want.

"GK""RB""LB""CB""DCM""RM""LM""CM""ACM""SC"

Formation Presets

Formations which already exist as presets within the game can be used in custom databases simple by referring to a preset's identifier (all listed below):

"4-4-2""4-4-2 W""4-4-2 D""4-2-3-1""4-3-3""4-1-4-1""4-4-1-1""3-4-3""3-5-2"

Formation Nodes

Custom formations can be defined as arrays of Formation Nodes. Each Formation Nodes describes where a given outfield player will position themselves on the field. Players will position themselves based on the grid below (note that it is rotated 180° compared to the in-game formation editor):

   RIGHT ← ← ← ← LEFT
   0 1 2 3 4 5 6 7 8
 0                    DEFENCE
 1                      ↓
 2                      ↓
 3                      ↓
 4                      ↓
 5                      ↓
 6                      ↓
 7                      ↓
 8                      ↓
 9                      ↓ 
10                    ATTACK

Formation Nodes contain two separate positions: one describing where a player will be when their team is in possession, and another describing where they'll be when their team is out of possession (i.e. a more defensive position).

[
    [ x1, y1 ], // Position when in possession.
    [ x2, y2 ]  // Position when out of possession.
]

So for example, let's consider the following Formation Node for a left-back:

[
    [ 8, 2 ],
    [ 6, 0 ]
]

In possession, this player will position themselves at [ 8, 2 ] (i.e. x = 8, y = 2). This player is playing extremely wide; x = 8 is the left-most co-ordinate on the grid. y = 2 is about halfway between the centre backs and the midfield, so this player is presumably expected to provide support to wide midfielders.

Out of possession however, this player will position themselves at [ 6, 0 ] (i.e. x = 6, y = 0). This is a narrower position than their in-possession position; x = 6 is much closer to the centre of defence (and the goal) and y = 0 is as close to the goal line as a player can be, so this player is clearly expected to drop much deeper when their team loses the ball.

Example formation node.

The fact that players can have both an attacking position and a defensive position offers a lot of tactical flexibility and fluidity. However, bear in mind that a player will run between their two positions whenever the ball changes between teams, so a large distance between the positions will increase the risk that the player will be caught out of possession when their team loses the ball!


Typescript-style Schema

If you're familiar with Typescript and would prefer to understand database schemas as Typescript types, a rough set of types can be found below. Note that int is not actually a valid Typescript type, but is used here instead of number for clarity.

// The game uses colours from the AAP-64 palette.
// See: https://lospec.com/palette-list/aap-64
type KitColor =
    | "BLACK" // #060608
    | "BLUE_DARK" // #143464
    | "BLUE_LIGHT" // #249fde
    | "BLUE_ROYAL" // #285cc4
    | "BLUE_SLATE" // #849be4
    | "BLUE_SLATE_DARK" // #588dbe
    | "BROWN" // #71413b
    | "BROWN_LIGHT" // #bb7547
    | "GREEN_DARK" // #1a7a3e
    | "GREEN_FOREST" // #14a02e
    | "GREEN_KIWI" // #9cdb43
    | "GREEN_LIGHT" // #59c135
    | "GREEN_PINE" // #24523b
    | "GREY_DARK" // #6d758d
    | "GREY_LIGHT" // #b3b9d1
    | "LAVENDER" // #b9bffb
    | "LAVENDER_LIGHT" // #e3e6ff
    | "MAROON" // #73172d
    | "ORANGE_DARK" // #fa6a0a
    | "ORANGE_LIGHT" // #f9a31b
    | "PINK_DARK" // #e86a73
    | "PINK_LIGHT" // #f5a097
    | "PURPLE" // #793a80
    | "PURPLE_DARK" // #403353
    | "PURPLE_LIGHT" // #bc4a9b
    | "RED" // #b4202a
    | "TURQUOISE_LIGHT" // #a6fcdb
    | "WHITE" // #ffffff
    | "YELLOW" // #fffc40;

type Kit = {
    // Each kit style will appear differently in game:
    //     * "SOLID": the primary shirt colour will fill the torso area, and the secondary shirt
    //       colour will fill the sleeves.
    //     * "STRIPES": the primary and secondary shirt colours will be arranged in vertical stripes.
    //     * "HOOPS": the primary and secondary shirt colours will be arranged in horizontal stripes.
    // Note that a plain/single-colour shirt can be achieved by making `shirt_primary` and
    // `shirt_secondary` the same for any style.
    style: "SOLID" | "STRIPES" | "HOOPS";
    shirt_primary: KitColor;
    shirt_secondary: KitColor;
    shorts: KitColor;
    socks: KitColor;
};

type FormationNode = [
    // Player grid position when team is in possession.
    [int, int],
    // Player grid position when team is out of possession.
    [int, int],
];

type FormationPreset =
    | "4-4-2"
    | "4-4-2 W"
    | "4-4-2 D"
    | "4-2-3-1"
    | "4-3-3"
    | "4-1-4-1"
    | "4-4-1-1"
    | "3-4-3"
    | "3-5-2";

// Formations can either use a preset already in the game, or define a custom formation by
// describing each 'node' which will be occupied by a specific player. Note that if defining
// a custom formation:
//    * The goalkeeper is never included, and the formation should consist of exactly 10 nodes.
//    * Nodes are ordered back-to-front, right-to-left; the first node is the player closest
//      to the goalkeeper (presumably a defender), and the last node is the player furthest
//      upfield (presumably a striker).
type Formation = FormationPreset | Array<FormationNode>;

type Team = {
    // Any string can be used to identify a team.
    // e.g. "472", "EXAMPLE_UNITED", "P9IA12C" etc.
    id: string;
    // The full name, unshortened of the team (max 25 chars).
    // e.g. "Example United", "Farnsby City" etc.
    name: string;
    // A shortened version of the team's name.
    // e.g. "Example "Example Utd", "Farnsby" etc.
    short_name: string;
    kits: {
        home: Kit;
        away: Kit;
    },
    // The team's default starting formation. This can still be changed in-game when using the team.
    formation: Formation;
    // The IDs of the 11 players who will start matches for this team by default,
    // including one goalkeeper. Players are ordered back-to-front, right-to-left.
    first_xi: Array<string>;
};

type HairColor =
    | "BROWN_LIGHT"
    | "BROWN"
    | "BROWN_DARK"
    | "BLACK"
    | "GREY"
    | "BLOND"
    | "GINGER"
    | "GREEN"
    | "BLUE"
    | "RED";

// In addition to these values, you can also use custom hairstyles from local files or Steam Workshop.
// If the custom hairstyle isn't found by the game, the player will default to "BALD".
type Hairstyle =
    | "BALD"
    | "BALDING"
    | "AFRO"
    | "AFRO_SHORT"
    | "BOB"
    | "BRAIDS"
    | "BRAID_PONYTAIL"
    | "BUN"
    | "BUZZ"
    | "FANCY"
    | "FLATTOP"
    | "LONG"
    | "MESSY"
    | "MOHAWK"
    | "PONYTAIL"
    | "QUIFF"
    | "SHORT"
    | "SHOULDER_LENGTH"
    | "TIGHT_BUN";


// In addition to these values, you can also use custom facial hair from local files or Steam Workshop.
// If the custom facial hair style isn't found by the game, the player will default to "NONE".
type FacialHair =
    | "NONE"
    | "ANCHOR"
    | "CHINSTRAP"
    | "CIRCLE"
    | "FULL"
    | "GOATEE"
    | "MOUSTACHE_BIG"
    | "MOUSTACHE_SMALL"
    | "MUTTON_CHOPS"
    | "SHADOW"
    | "STUBBLE";

type PlayerInfo = {
    // A player can have a first name, last name, or both, but not neither. If only one
    // is provided, the game will assume they are mononymous (e.g. "Rivaldo", "Moses", "Cher").
    first_name?: string | null;
    last_name?: string | null;
    // Skin tone must be a integer from 1-5 (darkest to palest).
    skin: number;
    hair: HairColor;
    // Note that for both `hairstyle` and `facial_hair`, custom values can be used as long as
    // they are loaded correctly by the game (e.g. via Steam Workshop or local files). If the
    // game can't find the custom styles, the player will just have no hair or facial hair.
    hairstyle: Hairstyle;
    facial_hair: FacialHair;
};

// Note that although the game will indicate formation positions with other position types
// not listed here (e.g. "RWB", "LW"), they aren't actually eligible as player positions
// (so for example, a "RWB" player should actually be "RB"/"RM").
type Position =
    | "GK"
    | "RB"
    | "LB"
    | "CB"
    | "DCM"
    | "RM"
    | "LM"
    | "CM"
    | "ACM"
    | "SC";

// Each player stat must be an integer and can range from 1-10 inclusive. Any combination of
// stats is permitted, no matter how silly.
// Note that the player's average rating is automatically calculated by the game when the
// database is loaded.
type PlayerStats = {
    speed: int;
    control: int;
    passing: int;
    shooting: int;
    power: int;
    tackling: int;
    handling: int;
    agility: int;
};

type Player = {
    // Any string can be used to identify a player.
    // e.g. "731", "JANE_DOE", "HJ8DM3B" etc.
    id: string;
    // The player's names and appearance data.
    info: PlayerInfo;
    // A player with no `team_id` will be considered a Free Agent.
    team_id?: string | null;
    // `shirt_number` is only required if the player has a `team_id`. Free agents do not
    // require numbers (if one is provided, the game will ignore it).
    // Must be an integer if provided.
    shirt_number?: number | null;
    // Each player can have a maximum of 3 positions.
    positions: Array<Position>;
    stats: PlayerStats;
};

type Database = {
    // An optional name for the database to display in menus e.g. "World Nations", "Retro Clubs" etc.
    // If `display_name` isn't provided, the game will instead show the name of the database folder.
    display_name?: string;
    // Used to decide which gender to use for non-English strings e.g. "jugador" or "jugadora".
    // Defaults to "male" if omitted.
    gender?: "male" | "female";
    teams: Array<Team>;
    players: Array<Player>;
};