Backend Game Logic: Difference between revisions
Andrew.gore (talk | contribs) |
Andrew.gore (talk | contribs) |
||
Line 1,007: | Line 1,007: | ||
* Club influence holders get paid 10% of what a club receives from cup prize money (if any). | * Club influence holders get paid 10% of what a club receives from cup prize money (if any). | ||
This 10% is split between all the club influence holders and the more influence a user holds in the club, the bigger portion of the 10% that influencer receives. | ** This 10% is split between all the club influence holders and the more influence a user holds in the club, the bigger portion of the 10% that influencer receives. | ||
Note: Some or even all of this will go to the club if the club has a debt | ** Note: Some or even all of this will go to the club if the club has a debt | ||
* Each player in the club gets a bonus that equates to 0.1% of a club's cup prize money | * Each player in the club gets a bonus that equates to 0.1% of a club's cup prize money | ||
The more influence a user holds in the player, the bigger portion of the 0.1% that influence holder will receive. | ** The more influence a user holds in the player, the bigger portion of the 0.1% that influence holder will receive. | ||
Note: The players rating is further used to determine the payout; e.g. a 50 rated player will get half of that 0,1% | ** Note: The players rating is further used to determine the payout; e.g. a 50 rated player will get half of that 0,1% | ||
* The manager who wins the cup gets 10 manager points and the manager who is the runner up gets 5 manager points. | * The manager who wins the cup gets 10 manager points and the manager who is the runner up gets 5 manager points. |
Revision as of 21:07, 17 March 2025
This wiki walks through all of the backend game logic (GSP), starting at the game world's creation and the creation of its first season, all the way through a season's worth of fixtures, the end of season event and then the creation of a new season.
The match day process flow of pre‑match, match and post match is walked through as well as the daily player fitness increases.
Some source code snippets are provided in certain places to help explain the formulas that are used.
When the game world is created
For each club, the average rating of the best 11 players is calculated and stored. This value is immutable and later used for things such as calculating the average player rating of the league, which is also immutable.
It's stored as:
- clubs.rating_start
Then the first season is created.
Creating a new season
- Each club's transfer limits are reset for transfer in and transfer out.
- Each club's form is reset.
- Players' form, yellow cards, red cards, yellow/red cards, banned and stamina are all reset.
- A player's wage for the season is calculated based on his current rating.
If the player rating is below 60 then the wage is calculated using the formula below:
if (rating < 60) return (5 * rating) * moneybase_multiplier;
Where moneybase_multiplier is set to be 50,000 when the game world is created.
If the player value is 60 rated or above then it uses the formula below, basically starting at 1500 then increasing by 20% more than the previous rating. It uses the formula below:
AmountT wage = 300; for(int ic1 = 1; ic1 <= (rating-60); ic1++) { wage = ((wage * 12 ) / 10); } return wage * moneybase_multiplier;
A grid of all the wages to ratings can be seen in the table below:
Rating | Wage |
---|---|
50 | 1,250 |
51 | 1,275 |
52 | 1,300 |
53 | 1,325 |
54 | 1,350 |
55 | 1,375 |
56 | 1,400 |
57 | 1,425 |
58 | 1,450 |
59 | 1,475 |
60 | 1,500 |
61 | 1,800 |
62 | 2,160 |
63 | 2,592 |
64 | 3,110 |
65 | 3,732 |
66 | 4,479 |
67 | 5,375 |
68 | 6,450 |
69 | 7,740 |
70 | 9,288 |
71 | 11,145 |
72 | 13,374 |
73 | 16,049 |
74 | 19,259 |
75 | 23,111 |
76 | 27,733 |
77 | 33,279 |
78 | 39,935 |
79 | 47,922 |
80 | 57,506 |
81 | 69,008 |
82 | 82,809 |
83 | 99,371 |
84 | 119,245 |
85 | 143,094 |
86 | 171,713 |
87 | 206,056 |
88 | 247,267 |
89 | 296,720 |
90 | 356,064 |
91 | 427,277 |
92 | 512,733 |
93 | 615,279 |
94 | 738,335 |
95 | 886,002 |
96 | 1,063,203 |
97 | 1,275,843 |
98 | 1,531,012 |
99 | 1,837,214 |
- Each player's value is calculated and updated at the start of each season.
It uses the formula below:
AmountT players_value = wage * multiplier;
Where multiplier is set to be 75. So the value of a player is 75 weeks of his wages.
If the player is over 22 years old, then for each year of his age he loses 10% of his value compared to the previous year.
This stops when a player reaches 40 years of age, then players aged 40 and above are all valued the same as a 40 year old player. See the formula below:
if(players_age > 22) { if(players_age > 40) players_age = 40; for(int ic1 = 23; ic1 <= (players_age); ic1++) { players_value = (players_value * 9) / 10; } }
- Players' fitness is reset to be between 85–100, using the formula below:
const int fitness = 85 + rand_get_mod(15);
Creating all the competitions
The competitions are created for the domestic, continental and world tournaments.
Each country has a domestic cup and every club in the country is put into the cup. For the first round it is possible that the number of teams in the cup are not a power of 2, which means some clubs will not have an opponent and will need a bye to the next round. See the formula below:
int num_dummy_teams = 0; if (round == 1) { const int num_teams_needed = (1 << num_rounds); num_dummy_teams = num_teams_needed - num_teams; }
The leagues are created and the correct teams are put into them for each domestic league and the continental group stages.
If it is the first season then the clubs that go into all the domestic leagues, continental groups and the world club cup are pre‑defined and provided in a data file.
If it is not the first season then the clubs that go into the domestic leagues are based upon the previous seasons with any promotion and relegations factored in.
The continental groups will have the winners of all the previous season's domestic leagues put into the group stage. One spot is allocated for each country apart from ENG, GER and ESP, who each get 2 spots.
The world club cup will have the winners of the previous season's four continental competitions placed in it.
Setup the Economies for the Domestic Leagues and the Continental Competitions
The average league attendance for each league and the average player rating of the clubs in the league are both stored. See the formula below:
int ave_league_attendance; int ave_club_rating_start; const int ave_league_attendance = league_attendance / num_teams; const int ave_club_rating_start = league_rating / num_teams;
These values are immutable and all future seasons use the same values.
At the start of the first season, each club starts off with a balance that would be enough to pay half a season's worth of its wages. See the formula below:
int num_gameweek_wages_for_club_balance = (num_rounds * 5000 / 10000; AmountT start_balance = club_wages * num_gameweek_wages_for_club_balance;
When the first season is created, the following values are calculated for each domestic league and continental competition: ticket cost, TV money and prize money pot. As below:
AmountT ticket_cost; AmountT tv_money; AmountT prize_money_pot;
These values are immutable and all future seasons use the same values.
A season's worth of income for each domestic league is calculated by adding up a season's worth of outgoings for all the clubs in the league, which is the league's wage bill.
league_wages += (league_wages * 500) / 10000; //inflation rate AmountT weekly_ticket_pot = (league_wages * gate_reciepts_percentage) / 10000; AmountT weekly_tv_pot = (league_wages * tv_percentage) / 10000; int weekly_tickets_sold = league_attendance / 2; //halve games played at home const AmountT ticket_price = weekly_ticket_pot / weekly_tickets_sold; const AmountT weekly_tv_money = weekly_tv_pot / num_teams;
The income is set to match the outgoings so that by the end of the season if all the clubs' balances were added up, it would be around the same as at the start of a season (assuming no transfers had taken place).
A 5% inflation rate is added to the income so that there is a little extra money for each of the clubs.
The income is split 50/50 between TV revenue and Gate receipts (which is further split into sponsorship, merchandise). The ticket price for the league is calculated based on the number of fans expected to attend all the matches in a season.
The prize money pot for the league, which is distributed at the end of the season, is calculated by taking 12% of a season's worth of the league's wages.
int num_gameweek_wages_for_prize_pot = (num_rounds * 1200) / 10000; AmountT season_prize_money_pot = league_wages * num_gameweek_wages_for_prize_pot;
For the continental competitions, the same income calculation is done for each of the group stage leagues. However, the group with the highest TV Revenue, Ticket Price and Prize Money Pot within a continent is then chosen to be used for all the other groups too.
As previously mentioned, these values are all permanently stored and used for all the future seasons. This means that the season's income is always the same for each league and anchored around how the leagues' outgoings were (its wage bill) at the start of the game.
Schedules
The schedule for the game world as a whole will have 45 gameweeks, each game week falling on Saturdays and Wednesdays.
All leagues play their first game at the same time and their last game at the same date. As leagues all have a different number of clubs in them, they also have different numbers of fixtures during a season. Some leagues will have a few Wednesdays off, to spread the matches evenly.
Some gameweeks will have more than one match played in them as there could be cup games, either domestic, continental or world cup on a Monday.
The schedule can be seen here:
Week | Date | Weekday | Competition | Match Day | |
---|---|---|---|---|---|
1 | 15.07.2024 | Monday | BREAK | bye round in code | |
17.07.2024 | Wednesday | BREAK | bye round in code | ||
20.07.2024 | Saturday | League | 1 | ||
2 | 22.07.2024 | Monday | Continental 1 | ||
24.07.2024 | Wednesday | League | 2 | ||
27.07.2024 | Saturday | League | 3 | ||
3 | 29.07.2024 | Monday | Continental 2 | ||
31.07.2024 | Wednesday | League | 4 | bye round league | |
03.08.2024 | Saturday | League | 5 | ||
4 | 05.08.2024 | Monday | Cup1 | ||
07.08.2024 | Wednesday | League | 6 | bye round league | |
10.08.2024 | Saturday | League | 7 | ||
5 | 12.08.2024 | Monday | Continental 3 | ||
14.08.2024 | Wednesday | League | 8 | bye round league | |
17.08.2024 | Saturday | League | 9 | ||
6 | 19.08.2024 | Monday | Continental 4 | ||
21.08.2024 | Wednesday | League | 10 | bye round league | |
24.08.2024 | Saturday | League | 11 | ||
7 | 26.08.2024 | Monday | Continental 5 | ||
28.08.2024 | Wednesday | League | 12 | bye round league | |
31.08.2024 | Saturday | League | 13 | ||
8 | 02.09.2024 | Monday | Cup2 | bye round cup | |
04.09.2024 | Wednesday | League | 14 | bye round league | |
07.09.2024 | Saturday | League | 15 | ||
9 | 09.09.2024 | Monday | Continental 6 | ||
11.09.2024 | Wednesday | League | 16 | bye round league | |
14.09.2024 | Saturday | League | 17 | ||
10 | 16.09.2024 | Monday | Continental 7 | ||
18.09.2024 | Wednesday | League | 18 | bye round league | |
21.09.2024 | Saturday | League | 19 | ||
11 | 23.09.2024 | Monday | Cup3 | bye round cup | |
25.09.2024 | Wednesday | League | 20 | ||
28.09.2024 | Saturday | League | 21 | ||
12 | 30.09.2024 | Monday | Continental 8 | ||
02.10.2024 | Wednesday | League | 22 | bye round league | |
05.10.2024 | Saturday | League | 23 | ||
13 | 07.10.2024 | Monday | Cup4 | bye round cup | |
09.10.2024 | Wednesday | League | 24 | ||
12.10.2024 | Saturday | League | 25 | ||
14 | 14.10.2024 | Monday | Continental 9 | ||
16.10.2024 | Wednesday | League | 26 | bye round league | |
19.10.2024 | Saturday | League | 27 | ||
15 | 21.10.2024 | Monday | Cup5 | ||
23.10.2024 | Wednesday | League | 28 | ||
26.10.2024 | Saturday | League | 29 | ||
16 | 28.10.2024 | Monday | Continental 10 | ||
30.10.2024 | Wednesday | League | 30 | bye round league | |
02.11.2024 | Saturday | League | 31 | ||
17 | 04.11.2024 | Monday | Cup6 | bye round cup | |
06.11.2024 | Wednesday | League | 32 | ||
09.11.2024 | Saturday | League | 33 | ||
18 | 11.11.2024 | Monday | Club World 1 | ||
13.11.2024 | Wednesday | Club World 2 | bye round league | ||
16.11.2024 | Saturday | League | 34 | ||
19 | 18.11.2024 | Monday | Cup7 | bye round cup | |
20.11.2024 | Wednesday | League | 35 | ||
23.11.2024 | Saturday | League | 36 | ||
20 | 25.11.2024 | Monday | Continental 1/16 | ||
27.10.2024 | Wednesday | League | 37 | ||
30.11.2024 | Saturday | League | 38 | ||
21 | 02.12.2024 | Monday | Cup8 | bye round cup | |
04.12.2024 | Wednesday | League | 39 | ||
07.12.2024 | Saturday | League | 40 | ||
22 | 09.12.2024 | Monday | Continental QF | ||
11.12.2024 | Wednesday | League | 41 | ||
14.12.2024 | Saturday | League | 42 | ||
23 | 16.12.2024 | Monday | Continental SF | ||
18.12.2024 | Wednesday | League | 43 | ||
21.12.2024 | Saturday | League | 44 | ||
24 | 23.12.2024 | Monday | Cup9 | ||
25.12.2024 | Wednesday | League | 45 | ||
28.12.2024 | Saturday | Continental F | |||
25 | 30.12.2024 | Monday | BREAK | bye round in code | |
01.01.2025 | Wednesday | BREAK | bye round in code | ||
04.01.2025 | Saturday | BREAK | |||
1/26 | 06.01.2025 | Monday | BREAK | ||
08.01.2025 | Wednesday | BREAK | |||
11.01.2025 | Saturday | League | 1 |
Each season will start on either the second Saturday of January or the second Saturday of July: See the dates for the first 10 seasons:
Season 1: start: 2025-1-11 0:0:0 (1736553600) end: 2025-7-12 0:0:0 (1752278400) first turn: 2025-1-18 6:0:0 (1737180000) last turn: 2025-6-29 0:0:0 (1751155200) Season 2: start: 2025-7-12 0:0:0 (1752278400) end: 2026-1-10 0:0:0 (1768003200) first turn: 2025-7-19 6:0:0 (1752904800) last turn: 2025-12-28 0:0:0 (1766880000) Season 3: start: 2026-1-10 0:0:0 (1768003200) end: 2026-7-11 0:0:0 (1783728000) first turn: 2026-1-17 6:0:0 (1768629600) last turn: 2026-6-28 0:0:0 (1782604800) Season 4: start: 2026-7-11 0:0:0 (1783728000) end: 2027-1-9 0:0:0 (1799452800) first turn: 2026-7-18 6:0:0 (1784354400) last turn: 2026-12-27 0:0:0 (1798329600) Season 5: start: 2027-1-9 0:0:0 (1799452800) end: 2027-7-10 0:0:0 (1815177600) first turn: 2027-1-16 6:0:0 (1800079200) last turn: 2027-6-27 0:0:0 (1814054400) Season 6: start: 2027-7-10 0:0:0 (1815177600) end: 2028-1-8 0:0:0 (1830902400) first turn: 2027-7-17 6:0:0 (1815804000) last turn: 2027-12-26 0:0:0 (1829779200) Season 7: start: 2028-1-8 0:0:0 (1830902400) end: 2028-7-8 0:0:0 (1846627200) first turn: 2028-1-15 6:0:0 (1831528800) last turn: 2028-6-25 0:0:0 (1845504000) Season 8: start: 2028-7-8 0:0:0 (1846627200) end: 2029-1-6 0:0:0 (1862352000) first turn: 2028-7-15 6:0:0 (1847253600) last turn: 2028-12-24 0:0:0 (1861228800) Season 9: start: 2029-1-6 0:0:0 (1862352000) end: 2029-7-7 0:0:0 (1878076800) first turn: 2029-1-13 6:0:0 (1862978400) last turn: 2029-6-24 0:0:0 (1876953600) Season 10: start: 2029-7-7 0:0:0 (1878076800) end: 2030-1-5 0:0:0 (1893801600) first turn: 2029-7-14 6:0:0 (1878703200) last turn: 2029-12-23 0:0:0 (1892678400)
Stadium Building and Fanbase Changes
Under certain circumstances the stadiums will auto build a little bit at the start of each season, aiming to have a stadium capacity target of either the club's fanbase + 30% or the average attendance of the league + 30%, whichever is smaller.
int target_attendance; if (ave_attendance < fans_current) target_attendance = (ave_attendance * 130) / 100; else target_attendance = (fans_current * 130) / 100;
If the current size of the stadium is smaller than the target attendance then seat capacity will be added somewhere between the current size and the target. Note, this will not happen if the number of new seats to be added is less than 200. See the formula below:
int new_size = stadium_size_current; int increase = 0; if (stadium_size_current < target_attendance) { const int max_inc = target_attendance - stadium_size_current; const int min_inc = max_inc / 2; increase = min_inc + rand_get_mod(max_inc - min_inc); if (increase > 200) { new_size = stadium_size_current + increase; } }
For example: This means that all clubs in the lower leagues have the potential to have a stadium size sufficient enough to hold the average number of fans in the top league + 30%, if they got into that league and stayed in there, allowing them to compete on a more even basis.
Under certain circumstances, at the start of each season, the fan base can rise and fall depending on if the club has been promoted or relegated.
If the club is currently in the division that it started in then its fanbase can rise, tending towards either the league's average attendance or the fanbase it started the game with, whichever is greater.
if (club_division == division_start) { //move to league average or start fanbase (whichever is bigger) if (fans_start > ave_attendance) target_fanbase = fans_start; else target_fanbase = ave_attendance; }
If the club is not in the division that it started in, then it has either been promoted or relegated and its target fanbase will change accordingly. See the formula below:
else //In non-starting league { if (fans_start > ave_attendance) target_fanbase = fans_start - (fans_start - ave_attendance) / 2; else target_fanbase = ave_attendance; if (club_division > division_start) { //got relegated } else if (club_division < division_start) { if (target_fanbase < fans_start) target_fanbase = fans_start; } }
Each season the fan base will tend from its current fan base towards its target by 50%.
int new_attendance = fans_current; if (fans_current > target_fanbase) //shrink it new_attendance = fans_current - (fans_current - target_fanbase) / 2; if (fans_current < target_fanbase) //grow it new_attendance = fans_current + (target_fanbase - fans_current) / 2;
For example: If a club had a fanbase of 1000 and got promoted, its fanbase would slowly rise each season until it was at the average fanbase of the league that it is now in. An English division 10 club with a 1000 fanbase could eventually have a fanbase of over 35,000 if it got into and stayed in the English division 1.
As mentioned before, the stadium would also gradually increase its capacity, alongside the fanbase.
Fitness
On each matchday, fitness is reduced for each player during the match.
It uses the formula below:
reduce_level = (1 * time_played) / 15; new_fitness = start_fitness - reduce_level - rand_get_mod(6);
Basically if he played 90 minutes he would lose (randomly) between 24 and 29 fitness points, which is on average 26/27 a match. Of course if he played less, say 45 minutes, then he would lose half of that, on average.
Then each day at 3am UTC time, fitness increases with:
new_fitness = fitness + 5 + rand_get_mod(5);
So it goes up between 5 and 9 points each day, which is on average 7 a day.
As an example: If a player's fitness was 100% and he played two league games a week, say Saturday and Wednesday, he would go down 53 points on average (e.g. 26+27) and up 49 a week on average (e.g. plus 7 for 7 days).
Gradually over a season the player will reduce and will need to be rotated. If the club plays in cup games too then they will reduce even faster, so squad rotation is even more important if the club is also aiming to win cups.
Note: A player's fitness will increase more slowly if he is injured but back in the next two weeks. See below:
if (injured > timestamp) { fitness += 3 + rand_get_mod(5); }
If the player is injured for two weeks or more then his fitness will not increase.
Matchday
All the fixtures that have not yet played are identified and prepared for processing their matchdays.
For each fixture, the tactics and teamsheets for both teams are processed and validated before the match.
If any player is unhappy then they lose 10% of their rating and if a player is injured or banned then he is considered to be a "dud player" and will not take part in the match.
If the match is a continental competition then the ticket price and TV revenue numbers that are specific to that competition are used. All other competitions use the ticket price and TV revenue numbers that the home team uses for its domestic league games. Each domestic league has its own ticket price and TV revenue that all clubs in that league use.
Attendance
The number of fans that turn up for matches are as follows:
Domestic league and cup games will use the home club's fanbase, assuming they fit into the home club's stadium, as the number of fans that will turn up on matchday.
The fanbase does not grow or shrink if you win or lose games; however, after the sixth domestic league game in the season, the attendance for domestic games can increase by up to 30% from its fanbase depending on how high in the table the club is above the middle of the table, and up to 30% less depending on how low the club is from the middle of the table. See the formula below:
//change the attendance based on the club's domestic league position if (games_played > 6) { int mid_table = num_teams / 2; //Get the league table size and half it. //if in top half of table means more fans if (new_position < mid_table) { int units_of_fans = mid_table - new_position; c->fans = c->fans + ((c->fans / 30) * units_of_fans); } else if(new_position > mid_table) //bottom half of the table means less fans { int units_of_fans = new_position - mid_table; c->fans = c->fans - ((c->fans / 30) * units_of_fans); } }
If it's a domestic cup or shield game then they usually get more fans than a league game, so it looks to use either the fan base (with the 30% modifier if applicable) or 75% of the stadium size, whichever is bigger. See the formula below:
if((comp_type == COMP_TYPE_NATIONAL_CUP) || (comp_type == COMP_TYPE_NATIONAL_SHIELD)) { int minimum_fans = (c->stadium_size / 4) * 3; if (c->fans < minimum_fans) { c->fans = minimum_fans - rand_get_mod(100); } }
Continental and world club cup games almost always have full stadium attendances.
Tactics Autopick
If the club has no manager or it has a manager who has submitted invalid tactics, then the autopick will kick in.
Invalid tactics are:
- Contains at least one player who is injured or match banned.
- Has no goalkeeper in the goalkeeper position – unless there is a good reason like all the goalkeepers in the squad are injured or banned.
The autopick will randomly choose one of the following formations: 4-4-2, 4-3-3 or 4-5-1 and then find the best possible player for each of the positions in the chosen formation. The player's fitness will also be taken into consideration when choosing the best players for each position.
Player Rating Adjustments
Each player is checked if it is being played in the correct position that it is meant to be played in; for example, a DC in a DC position.
If the player is not in the correct position then he is penalized. The further out of position a player is the more he is penalized. The grid below shows what percentage should be knocked off the player's goalkeeping, tackling, passing and shooting rating, should he be out of position. See:
const int g_grid_outta_pos_multi[16][9][7] = { { {0, 0, 0, 0, 0, 0, 0}, //G {40, 40, 40, 40, 40, 40, 40}, {40, 40, 40, 40, 40, 40, 40}, {40, 40, 40, 40, 40, 40, 40}, {40, 40, 40, 40, 40, 40, 40}, {40, 40, 40, 40, 40, 40, 40}, {40, 40, 40, 40, 40, 40, 40}, {40, 40, 40, 40, 40, 40, 40}, {40, 40, 40, 40, 40, 40, 40} }, { {40, 40, 40, 40, 40, 40, 40}, //LB {3, 3, 5, 5, 5, 0, 0}, {3, 3, 5, 5, 5, 0, 0}, {3, 3, 5, 5, 5, 0, 0}, {5, 5, 10, 10, 10, 0, 0}, {10, 15, 15, 15, 15, 10, 5}, {25, 25, 25, 25, 25, 25, 20}, {35, 35, 35, 35, 35, 35, 35}, {35, 35, 35, 35, 35, 35, 35} }, { {40, 40, 40, 40, 40, 40, 40}, //CB {5, 0, 0, 0, 0, 0, 5}, {5, 0, 0, 0, 0, 0, 5}, {10, 10, 5, 5, 5, 10, 10}, {10, 10, 5, 5, 5, 10, 10}, {15, 15, 15, 15, 15, 15, 15}, {25, 25, 25, 25, 25, 25, 25}, {35, 35, 35, 35, 35, 35, 35}, {35, 35, 35, 35, 35, 35, 35} }, { {40, 40, 40, 40, 40, 40, 40}, //RB {0, 0, 5, 5, 5, 3, 3}, {0, 0, 5, 5, 5, 3, 3}, {0, 0, 5, 5, 5, 3, 3}, {0, 0, 10, 10, 10, 5, 5}, {5, 10, 15, 15, 15, 15, 10}, {20, 25, 25, 25, 25, 25, 25}, {35, 35, 35, 35, 35, 35, 35}, {35, 35, 35, 35, 35, 35, 35} }, { {40, 40, 40, 40, 40, 40, 40}, //DML {10, 10, 15, 15, 15, 5, 5}, {10, 10, 15, 15, 15, 5, 5}, {2, 2, 2, 2, 1, 0, 0}, {2, 2, 2, 2, 1, 0, 0}, {3, 3, 3, 3, 2, 0, 0}, {15, 15, 15, 15, 10, 5, 5}, {25, 25, 25, 25, 25, 25, 25}, {25, 25, 25, 25, 25, 25, 25} }, { {40, 40, 40, 40, 40, 40, 40}, //DMC {15, 15, 10, 10, 10, 15, 15}, {15, 15, 10, 10, 10, 15, 15}, {3, 0, 0, 0, 0, 0, 3}, {3, 0, 0, 0, 0, 0, 3}, {3, 0, 0, 0, 0, 0, 3}, {15, 15, 10, 10, 10, 15, 15}, {25, 15, 15, 15, 15, 15, 25}, {25, 25, 25, 25, 25, 25, 25} }, { {40, 40, 40, 40, 40, 40, 40}, //DMR {5, 5, 15, 15, 15, 10, 10}, {5, 5, 15, 15, 15, 10, 10}, {0, 0, 1, 2, 2, 2, 2}, {0, 0, 1, 2, 2, 2, 2}, {0, 0, 2, 3, 3, 3, 3}, {5, 5, 10, 15, 15, 15, 15}, {25, 25, 25, 25, 25, 25, 25}, {25, 25, 25, 25, 25, 25, 25} }, { {40, 40, 40, 40, 40, 40, 40}, //LM {15, 15, 15, 15, 15, 15, 15}, {15, 15, 15, 15, 15, 15, 15}, {5, 5, 5, 5, 4, 0, 0}, {3, 3, 3, 3, 2, 0, 0}, {1, 1, 1, 1, 1, 0, 0}, {3, 3, 3, 3, 2, 0, 0}, {10, 10, 10, 10, 10, 10, 10}, {15, 15, 15, 15, 15, 15, 15} }, { {40, 40, 40, 40, 40, 40, 40}, //CM {15, 15, 15, 15, 15, 15, 15}, {15, 15, 15, 15, 15, 15, 15}, {5, 5, 1, 1, 1, 5, 5}, {3, 3, 0, 0, 0, 3, 3}, {3, 0, 0, 0, 0, 0, 3}, {3, 1, 0, 0, 0, 1, 3}, {15, 5, 5, 5, 5, 5, 15}, {15, 15, 15, 15, 15, 15, 15} }, { {40, 40, 40, 40, 40, 40, 40}, //RM {15, 15, 15, 15, 15, 15, 15}, {15, 15, 15, 15, 15, 15, 15}, {0, 0, 4, 5, 5, 5, 5}, {0, 0, 2, 3, 3, 3, 3}, {0, 0, 1, 1, 1, 1, 1}, {0, 0, 2, 3, 3, 3, 3}, {10, 10, 10, 10, 10, 10, 10}, {15, 15, 15, 15, 15, 15, 15} }, { {40, 40, 40, 40, 40, 40, 40}, //AML {35, 35, 35, 35, 35, 35, 35}, {35, 35, 35, 35, 35, 35, 35}, {25, 25, 35, 25, 25, 25, 25}, {15, 15, 15, 15, 15, 15, 10}, {5, 5, 5, 5, 5, 0, 0}, {5, 5, 3, 3, 1, 0, 0}, {5, 5, 3, 3, 1, 0, 0}, {10, 10, 10, 10, 5, 3, 3} }, { {40, 40, 40, 40, 40, 40, 40}, //AM {25, 25, 25, 25, 25, 25, 25}, {25, 25, 25, 25, 25, 25, 25}, {15, 15, 15, 15, 15, 15, 15}, {15, 15, 15, 15, 15, 15, 15}, {3, 0, 0, 0, 0, 0, 3}, {3, 0, 0, 0, 0, 0, 3}, {3, 0, 0, 0, 0, 0, 3}, {10, 10, 10, 10, 10, 10, 10} }, { {40, 40, 40, 40, 40, 40, 40}, //AMR {35, 35, 35, 35, 35, 35, 35}, {35, 35, 35, 35, 35, 35, 35}, {25, 25, 35, 25, 25, 25, 25}, {10, 15, 15, 15, 15, 15, 15}, {0, 0, 5, 5, 5, 5, 5}, {0, 0, 1, 3, 3, 5, 5}, {0, 0, 1, 3, 3, 5, 5}, {3, 3, 5, 10, 10, 10, 10} }, { {40, 40, 40, 40, 40, 40, 40}, //FL {35, 35, 35, 35, 35, 35, 35}, {35, 35, 35, 35, 35, 35, 35}, {35, 35, 35, 35, 35, 35, 35}, {25, 25, 25, 25, 25, 25, 25}, {15, 15, 15, 15, 15, 15, 15}, {5, 5, 5, 5, 5, 5, 5}, {3, 2, 2, 1, 1, 0, 0}, {3, 2, 2, 1, 1, 0, 0} }, { {40, 40, 40, 40, 40, 40, 40}, //FC {35, 35, 35, 35, 35, 35, 35}, {35, 35, 35, 35, 35, 35, 35}, {35, 35, 35, 35, 35, 35, 35}, {25, 25, 25, 25, 25, 25, 25}, {15, 15, 15, 15, 15, 15, 15}, {5, 5, 5, 5, 5, 5, 5}, {3, 1, 0, 0, 0, 1, 3}, {3, 1, 0, 0, 0, 1, 3} }, { {40, 40, 40, 40, 40, 40, 40}, //FR {35, 35, 35, 35, 35, 35, 35}, {35, 35, 35, 35, 35, 35, 35}, {35, 35, 35, 35, 35, 35, 35}, {25, 25, 25, 25, 25, 25, 25}, {15, 15, 15, 15, 15, 15, 15}, {5, 5, 5, 5, 5, 5, 5}, {0, 0, 1, 1, 2, 3, 3}, {0, 0, 1, 1, 2, 3, 3} } };
Player ratings are also adjusted when taking into consideration each player's tempo, tackling style and morale.
The tackling style can be set to be more aggressive, which means they will tackle better but also increase their chances of getting a yellow or red card.
const int tackling_style_adjuster = 2000; if(tackling_style == 0) { c->ply[ply_off].rating_aggression = c->ply[ply_off].rating_aggression - ((c->ply[ply_off].rating_aggression * tackling_style_adjuster) / 10000); if(c->ply[ply_off].rating_aggression < 10) c->ply[ply_off].rating_aggression = 10; } else if(tackling_style == 2) { c->ply[ply_off].rating_aggression = c->ply[ply_off].rating_aggression + ((c->ply[ply_off].rating_aggression * tackling_style_adjuster) / 10000); if(c->ply[ply_off].rating_aggression > 99) c->ply[ply_off].rating_aggression = 99; }
The tempo sets the player's rating and stamina. A higher tempo means that the player plays better although his fitness will drop more and faster. A lower tempo does the opposite on the ratings and stamina.
Morale when it is set to "low" will knock 10% off the player's ratings.
Match Results
After the match has been played, certain calculations are made and stored.
Manager points are awarded; 3 for a win and 1 for a draw.
Gate Receipts
For visual effects, the gate receipts are distributed between gate receipts, sponsorship and merchandise, with the combined total actually going 20% over. This extra 20% is then taken off through ground maintenance. See below:
AmountT gate_receipts = (AmountT)((c[0].fans * c[0].ticket_cost) * 0.8); const AmountT sponsor = (AmountT)((c[0].fans * c[0].ticket_cost) * 0.3); const AmountT merchandise = (AmountT)((c[0].fans * c[0].ticket_cost) * 0.1); const AmountT ground_maintenance = (AmountT)((c[0].fans * c[0].ticket_cost) * 0.2);
For the domestic cup games, continental knockouts and world club cup, the gate receipts are split evenly between the home team and the away team. There is only TV money for the continental knockout games and it is the same amount for both the home and away team.
Payouts
Payouts to users are made for the club/player influencers and the manager and agent wages.
If it was a domestic league game then:
- 0.2% of the player's wage is paid out between its influencers.
- 0.002% of the player's wage is paid out to the agent (if the player has one).
- If the club has a manager that is not locked and is active, then it gets paid a wage of 0.0004% of the club's TV income.
- If the club is not in debt before the game then 1% of the club's income is paid out between its influencers. This comes out of the club's balance.
If it was a domestic cup, continental game or world club cup then:
- If the club is not in debt before the game then 1% of the club's income is paid out between its influencers. This comes out of the club's balance.
Bonuses
- If the player starts the match then a starting lineup bonus of 0.05% of the player's wage is passed onto his influencers. A minimum of 45 minutes need to be played.
- If the player scored then a goal bonus of 0.1% of the player's wage is passed onto his influencers for each goal scored.
- If the player assists then an assist bonus of 0.1% of the player's wage is passed onto his influencers for each assist.
- If there was a clean sheet then the players who played in goals and defence who were in the starting eleven get a clean sheet bonus of 0.1% of the player's wage.
The player needs to have played at least 75 minutes of the game.
Fitness
Fitness is reduced for all players that played in the match. The more minutes they play the more fitness they lose. Goalkeepers lose a lot less, currently four times less, than outfield players. See below:
int reduce_level; if (ic2 > 0) reduce_level = (4 * time_played) / 15; else reduce_level = (1 * time_played) / 15; fitness = (fitness < 35 ? 1 : fitness - reduce_level - rand_get_mod(6));
Players can get injured for between 2 and 160 days, although it is weighted with more chance towards the lower end. See below:
injury_days = rand_get_mod( rand_get_mod (rand_get_mod (rand_get_mod (159) + 1) + 1) + 1) + 2;
The more days a player has been injured, the more fitness he will lose. See:
if (injury_days > 120) { reduce_level = 40 + rand_get_mod(5); } else if (injury_days > 80) { reduce_level += 30 + rand_get_mod(5); } else if (injury_days > 40) { reduce_level += 10 + rand_get_mod(5); }
Cup Final Prize Monday and Payouts
During the season, cup finals are played for domestic, continental and world tournaments that result in prize money for the clubs and payouts for the influence holders.
- The cup prize pots are calculated at the start of the game by taking the average starting balance of the 10 clubs in the cup that have the biggest starting balance. This value is then used for that cup's prize pot for all future seasons.
- From the cup prize pot, the club gets 25% if it is the winner and 12.5% if it is the runner up.
- Club influence holders get paid 10% of what a club receives from cup prize money (if any).
- This 10% is split between all the club influence holders and the more influence a user holds in the club, the bigger portion of the 10% that influencer receives.
- Note: Some or even all of this will go to the club if the club has a debt
- Each player in the club gets a bonus that equates to 0.1% of a club's cup prize money
- The more influence a user holds in the player, the bigger portion of the 0.1% that influence holder will receive.
- Note: The players rating is further used to determine the payout; e.g. a 50 rated player will get half of that 0,1%
- The manager who wins the cup gets 10 manager points and the manager who is the runner up gets 5 manager points.
End of Season
When the last fixture of the season has been played, prize money is paid out to each of the clubs in all of the domestic leagues.
Manager points are awarded; 10 for 1st place in a league and 5 for second place.
The first 50% of the league prize money pot is split equally between the clubs.
The other 50% of the total league prize money pot is then split in a linear way with the 1st placed club getting the most and the last placed club getting the least. For example, a league with 20 clubs would have the percentage split as follows: 1st: 9.5%......10th: 5%.......20th: 0.5%. See the formula below:
const int equal = 100 / num_teams; int percdue = (num_teams - position) * equal; int potperc = ((percdue << 8) / 100) * (2 * equal); if (potperc == 0) { percdue = 100 - (num_teams - 1) * equal; potperc = ((percdue << 8) / 100) * (2 * equal); } AmountT prize_money = (potperc / 100) * prize_money_pot; prize_money = ((prize_money >> 8));
clubs_amount = (prize_money * 5000) / 10000;
10% of a club's league prize money is split between all club influencers, provided the club isn't in debt.
shareholders_amount = prize_money - clubs_amount; shareholders_amount = (shareholders_amount * 1000) / 10000;
0.1% of a club's league prize money is for the influencers of each player, discounted with the player's rating.
const AmountT the_prize_share = (prize_money * 5) / 10000; AmountT sent_prize_share = (the_prize_share * player_rating) / 100;
The season then remains the current season for a few more days, before a new season is created.
Events Between Seasons
All the other end of season events (such as contracts, auctioning players) is done only at the point when the new season is created and the old season is replaced by it.
A ProcessSeasonEnd script is run
void ProcessSeasonEnd (DbHandleData& db, const IdT seasonId)
Taking each of the events within it in turn:
Updating the player data
The players database is updated at the end of each season, just before the new season is created.
New players are added, existing players updated and some players retire.
Adding new players:
- If a player in real life has been playing for a club, then he will get added to the game and to the free bench.
Updating players:
- Some of the current players will have their rating, position, side and influence price adjusted, all depending on how they have been performing in real life since the last update. Players that didn't have a date of birth set previously might get it added.
Retiring players:
- When a player retires he leaves his current club and he goes on the freebench, however, he can not be bought by anyone.
- All transfer auctions involving the player are cancelled.
- Users are no longer able to buy influence from the game for this player.
- However, users can trade any existing influence in this player between themselves.
- Since the player can not play for any club, he will not generate any influence payouts.
All players will also have their value (and wage if on the free bench and not on an active contract) recalculated, as they are now a bit older and potentially worth a little less, and also the ratings will have changed.
Player Contracts
- Every player who is at a club has his contract reduced by one season.
- If the contract runs out then the player gets moved to the free bench.
- However, under certain conditions the contract will get auto-renewed for one season:
- Players set to "do not renew" by the agent will never be auto-renewed and will definitely leave.
- If the club is not actively managed or the manager is locked, then all other player contracts are auto-renewed.
- If the manager is active, up to 2 players may leave, and all others get auto-renewed.
- Worst players are released first, and only players may leave in whose position the squad has enough other players.
Clubs in Debt - Auto Auction
If a club is in debt then it will attempt to sell off the club's best player not already in an active auction by putting him on the transfer market auction with a minimum bid of 1 SVC. In the very unlikely event that no player is available (e.g. because all players in the club retired or had their contracts run out) nothing happens.
This auction is started immediately (ending in a week, not started only when a bid is made). If no bid is made within the week, then the player leaves to the free bench.
There are three reasons for this:
- It will reduce the club's wage bill so that its costs are lower in the future.
- Other clubs may decide to bid and buy the player on the auction system, potentially getting some money into the club.
- Losing the best player is an incentive to manage the club's finances properly.
Fixing Squads
For all clubs, the overall squad size and structure (minimum players for each position) is fixed.
The minimum parameters can be seen below:
const unsigned MIN_TEAM_SIZE = 21; const std::map<RequiredSquadPosition, unsigned> MIN_TEAM_PER_POS = { {RequiredSquadPosition::GK, 2}, {RequiredSquadPosition::DEFENDER, 5}, {RequiredSquadPosition::MIDFIELD, 5}, {RequiredSquadPosition::ATTACKER, 3}, };
The worst players on the freebench are chosen as long as no other club are bidding for them. They are transferred for free to the club.
Players wages and values
- The values for all players are recalculated, based on possible rating changes and their changes in age for the current season.
- The wages for all players on the free bench are calculated.
(Players at a club have their wages fixed until a new contract gets signed.)
Club Dilution Events
Club dilutions are implemented but not active yet in the code! This is how they will work once activated:
- When a club is in debt at a season end a dilution event to raise SVC for the club is started immediately.
- When not in debt, a dilution proposal is created, and club influence holders can vote it down. This ensures that lost wallet keys won't make clubs permanently lost.
- The amount of new influence to be created in an event or proposal is 5%
- The amount of SVC raised is whatever users of the game bid for that 5% (put into the dilution pool). For these end-of-season dilutions, there is no minimum raise specified. It could be that one user gets all the 5% for just 1 SVC, but market forces will prevent that (and ensure a "fair value").