Esempio n. 1
0
 return g.dbl.tx("teamSeasons", "readwrite", function (tx) {
     return tx.teamSeasons.index('tid, season').iterate(backboard.bound([g.userTid], [g.userTid, '']), function (teamSeason) {
         if (teamSeason.season > g.season) {
             return tx.teamSeasons.delete(teamSeason.rid);
         }
     });
 });
Esempio n. 2
0
async function updateDeleteLeague({lid}: GetOutput): void | {[key: string]: any} {
    if (typeof lid !== 'number') {
        throw new Error('Invalid input for lid');
    }

    const db = await connectLeague(lid);
    try {
        const [numGames, numPlayers, teamSeasons, l] = await Promise.all([
            db.games.count(),
            db.players.count(),
            db.teamSeasons.index("tid, season").getAll(backboard.bound([0], [0, ''])),
            idb.meta.leagues.get(lid),
        ]);

        return {
            lid,
            name: l.name,
            numGames,
            numPlayers,
            numSeasons: teamSeasons.length,
        };
    } catch (err) {
        return {
            lid,
            name: undefined,
            numGames: undefined,
            numPlayers: undefined,
            numSeasons: undefined,
        };
    }
}
Esempio n. 3
0
        return tx.players.index('tid').iterate(backboard.lowerBound(g.PLAYER.FREE_AGENT), function (p) {
            var update;

            update = false;

            // Get player stats, used for HOF calculation
            return tx.playerStats.index("pid, season, tid").getAll(backboard.bound([p.pid], [p.pid, ''])).then(function (playerStats) {
                var age, excessAge, excessPot, pot;

                age = g.season - p.born.year;
                pot = p.ratings[p.ratings.length - 1].pot;

                if (age > maxAge || pot < minPot) {
                    excessAge = 0;
                    if (age > 34 || p.tid === g.PLAYER.FREE_AGENT) {  // Only players older than 34 or without a contract will retire
                        if (age > 34) {
                            excessAge = (age - 34) / 20;  // 0.05 for each year beyond 34
                        }
                        excessPot = (40 - pot) / 50;  // 0.02 for each potential rating below 40 (this can be negative)
                        if (excessAge + excessPot + random.gauss(0, 1) > 0) {
                            p = player.retire(tx, p, playerStats);
                            update = true;
                        }
                    }
                }

                // Update "free agent years" counter and retire players who have been free agents for more than one years
                if (p.tid === g.PLAYER.FREE_AGENT) {
                    if (p.yearsFreeAgent >= 1) {
                        p = player.retire(tx, p, playerStats);
                    } else {
                        p.yearsFreeAgent += 1;
                    }
                    p.contract.exp += 1;
                    update = true;
                } else if (p.tid >= 0 && p.yearsFreeAgent > 0) {
                    p.yearsFreeAgent = 0;
                    update = true;
                }

                // Heal injures
                if (p.injury.type !== "Healthy") {
                    // This doesn't use g.numGames because that would unfairly make injuries last longer if it was lower - if anything injury duration should be modulated based on that, but oh well
                    if (p.injury.gamesRemaining <= 82) {
                        p.injury = {type: "Healthy", gamesRemaining: 0};
                    } else {
                        p.injury.gamesRemaining -= 82;
                    }
                    update = true;
                }

                // Update player in DB, if necessary
                if (update) {
                    return p;
                }
            });
        });
Esempio n. 4
0
            return Promise.try(function () {
                // Only need scoutingRank for the user's team to calculate fuzz when ratings are updated below.
                // This is done BEFORE a new season row is added.
                if (tid === g.userTid) {
                    return tx.teamSeasons.index("tid, season").getAll(backboard.bound([tid, g.season - 3], [tid, g.season - 1])).then(function (teamSeasons) {
                        scoutingRank = finances.getRankLastThree(teamSeasons, "expenses", "scouting");

                        return teamSeasons[teamSeasons.length - 1];
                    });
                }

                return tx.teamSeasons.index("tid, season").get([tid, g.season - 1]);
            }).then(function (prevSeason) {
Esempio n. 5
0
        return player.genBaseMoods(tx).then(function (baseMoods) {
            // Reset contract demands of current free agents and undrafted players
            // KeyRange only works because g.PLAYER.UNDRAFTED is -2 and g.PLAYER.FREE_AGENT is -1
            return tx.players.index('tid').iterate(backboard.bound(g.PLAYER.UNDRAFTED, g.PLAYER.FREE_AGENT), function (p) {
                return player.addToFreeAgents(tx, p, g.PHASE.FREE_AGENCY, baseMoods);
            }).then(function () {
                // AI teams re-sign players or they become free agents
                // Run this after upding contracts for current free agents, or addToFreeAgents will be called twice for these guys
                return tx.players.index('tid').iterate(backboard.lowerBound(0), function (p) {
                    var contract, factor;

                    if (p.contract.exp <= g.season && (g.userTids.indexOf(p.tid) < 0 || g.autoPlaySeasons > 0)) {
                        // Automatically negotiate with teams
                        if (strategies[p.tid] === "rebuilding") {
                            factor = 0.4;
                        } else {
                            factor = 0;
                        }

                        if (Math.random() < p.value / 100 - factor) { // Should eventually be smarter than a coin flip
                            // See also core.team
                            contract = player.genContract(p);
                            contract.exp += 1; // Otherwise contracts could expire this season
                            p = player.setContract(p, contract, true);
                            p.gamesUntilTradable = 15;

                            eventLog.add(null, {
                                type: "reSigned",
                                text: 'The <a href="' + helpers.leagueUrl(["roster", g.teamAbbrevsCache[p.tid], g.season]) + '">' + g.teamNamesCache[p.tid] + '</a> re-signed <a href="' + helpers.leagueUrl(["player", p.pid]) + '">' + p.name + '</a> for ' + helpers.formatCurrency(p.contract.amount / 1000, "M") + '/year through ' + p.contract.exp + '.',
                                showNotification: false,
                                pids: [p.pid],
                                tids: [p.tid]
                            });

                            return p; // Other endpoints include calls to addToFreeAgents, which handles updating the database
                        }

                        return player.addToFreeAgents(tx, p, g.PHASE.RESIGN_PLAYERS, baseMoods);
                    }
                });
            });
        }).then(function () {
Esempio n. 6
0
async function updateLeaders(inputs, updateEvents, vm) {
    // Respond to watchList in case players are listed twice in different categories
    if (updateEvents.indexOf("dbChange") >= 0 || updateEvents.indexOf("watchList") >= 0 || (inputs.season === g.season && updateEvents.indexOf("gameSim") >= 0) || inputs.season !== vm.season()) {
        let [teamSeasons, players] = await Promise.all([
            g.dbl.teamSeasons.index("season, tid").getAll(backboard.bound([inputs.season], [inputs.season, ''])),
            g.dbl.players.getAll().then(players => {
                return player.withStats(null, players, {statsSeasons: [inputs.season]});
            }),
        ]);

        // Calculate the number of games played for each team, which is used later to test if a player qualifies as a league leader
        const gps = teamSeasons.map(teamSeason => {
            // Don't count playoff games
            if (teamSeason.gp > g.numGames) {
                return g.numGames;
            }
            return teamSeason.gp;
        });

        players = player.filter(players, {
            attrs: ["pid", "name", "injury", "watch"],
            ratings: ["skills"],
            stats: ["pts", "trb", "ast", "fgp", "tpp", "ftp", "blk", "stl", "min", "per", "ewa", "gp", "fg", "tp", "ft", "abbrev", "tid"],
            season: inputs.season,
        });

        const userAbbrev = helpers.getAbbrev(g.userTid);

        // minStats and minValues are the NBA requirements to be a league leader for each stat http://www.nba.com/leader_requirements.html. If any requirement is met, the player can appear in the league leaders
        const factor = (g.numGames / 82) * Math.sqrt(g.quarterLength / 12); // To handle changes in number of games and playing time
        const categories = [];
        categories.push({name: "Points", stat: "Pts", title: "Points Per Game", data: [], minStats: ["gp", "pts"], minValue: [70, 1400]});
        categories.push({name: "Rebounds", stat: "Reb", title: "Rebounds Per Game", data: [], minStats: ["gp", "trb"], minValue: [70, 800]});
        categories.push({name: "Assists", stat: "Ast", title: "Assists Per Game", data: [], minStats: ["gp", "ast"], minValue: [70, 400]});
        categories.push({name: "Field Goal Percentage", stat: "FG%", title: "Field Goal Percentage", data: [], minStats: ["fg"], minValue: [300]});
        categories.push({name: "Three-Pointer Percentage", stat: "3PT%", title: "Three-Pointer Percentage", data: [], minStats: ["tp"], minValue: [55]});
        categories.push({name: "Free Throw Percentage", stat: "FT%", title: "Free Throw Percentage", data: [], minStats: ["ft"], minValue: [125]});
        categories.push({name: "Blocks", stat: "Blk", title: "Blocks Per Game", data: [], minStats: ["gp", "blk"], minValue: [70, 100]});
        categories.push({name: "Steals", stat: "Stl", title: "Steals Per Game", data: [], minStats: ["gp", "stl"], minValue: [70, 125]});
        categories.push({name: "Minutes", stat: "Min", title: "Minutes Per Game", data: [], minStats: ["gp", "min"], minValue: [70, 2000]});
        categories.push({name: "Player Efficiency Rating", stat: "PER", title: "Player Efficiency Rating", data: [], minStats: ["min"], minValue: [2000]});
        categories.push({name: "Estimated Wins Added", stat: "EWA", title: "Estimated Wins Added", data: [], minStats: ["min"], minValue: [2000]});
        const stats = ["pts", "trb", "ast", "fgp", "tpp", "ftp", "blk", "stl", "min", "per", "ewa"];

        for (let i = 0; i < categories.length; i++) {
            players.sort((a, b) => b.stats[stats[i]] - a.stats[stats[i]]);
            for (let j = 0; j < players.length; j++) {
                // Test if the player meets the minimum statistical requirements for this category
                let pass = false;
                for (let k = 0; k < categories[i].minStats.length; k++) {
                    // Everything except gp is a per-game average, so we need to scale them by games played
                    let playerValue;
                    if (categories[i].minStats[k] === "gp") {
                        playerValue = players[j].stats[categories[i].minStats[k]];
                    } else {
                        playerValue = players[j].stats[categories[i].minStats[k]] * players[j].stats.gp;
                    }

                    // Compare against value normalized for team games played
                    if (playerValue >= Math.ceil(categories[i].minValue[k] * factor * gps[players[j].stats.tid] / g.numGames)) {
                        pass = true;
                        break;  // If one is true, don't need to check the others
                    }
                }

                if (pass) {
                    const leader = helpers.deepCopy(players[j]);
                    leader.i = categories[i].data.length + 1;
                    leader.stat = leader.stats[stats[i]];
                    leader.abbrev = leader.stats.abbrev;
                    delete leader.stats;
                    if (userAbbrev === leader.abbrev) {
                        leader.userTeam = true;
                    } else {
                        leader.userTeam = false;
                    }
                    categories[i].data.push(leader);
                }

                // Stop when we found 10
                if (categories[i].data.length === 10) {
                    break;
                }
            }

            delete categories[i].minStats;
            delete categories[i].minValue;
        }

        return {
            categories,
            season: inputs.season,
        };
    }
}
Esempio n. 7
0
    reader.onload = async event => {
        const uploadedFile = JSON.parse(event.target.result);

        // Get all players from uploaded files
        let players = uploadedFile.players;

        // Filter out any that are not draft prospects
        players = players.filter(p => p.tid === g.PLAYER.UNDRAFTED);

        // Get scouting rank, which is used in a couple places below
        const teamSeasons = await g.dbl.teamSeasons.index("tid, season").getAll(backboard.bound([g.userTid, g.season - 2], [g.userTid, g.season]));

        const scoutingRank = finances.getRankLastThree(teamSeasons, "expenses", "scouting");

        // Delete old players from draft class
        await g.dbl.tx(["players", "playerStats"], "readwrite", async tx => {
            await tx.players.index('tid').iterate(draftClassTid, p => tx.players.delete(p.pid));

            // Find season from uploaded file, for age adjusting
            let uploadedSeason;
            if (uploadedFile.hasOwnProperty("gameAttributes")) {
                for (let i = 0; i < uploadedFile.gameAttributes.length; i++) {
                    if (uploadedFile.gameAttributes[i].key === "season") {
                        uploadedSeason = uploadedFile.gameAttributes[i].value;
                        break;
                    }
                }
            } else if (uploadedFile.hasOwnProperty("startingSeason")) {
                uploadedSeason = uploadedFile.startingSeason;
            }

            let seasonOffset2 = seasonOffset;
            if (g.phase >= g.PHASE.FREE_AGENCY) {
                // Already generated next year's draft, so bump up one
                seasonOffset2 += 1;
            }

            const draftYear = g.season + seasonOffset2;

            // Add new players to database
            await Promise.map(players, async p => {
                // Make sure player object is fully defined
                p = player.augmentPartialPlayer(p, scoutingRank);

                // Manually set TID, since at this point it is always g.PLAYER.UNDRAFTED
                p.tid = draftClassTid;

                // Manually remove PID, since all it can do is cause trouble
                if (p.hasOwnProperty("pid")) {
                    delete p.pid;
                }

                // Adjust age
                if (uploadedSeason !== undefined) {
                    p.born.year += g.season - uploadedSeason + seasonOffset2;
                }

                // Adjust seasons
                p.ratings[0].season = draftYear;
                p.draft.year = draftYear;

                // Don't want lingering stats vector in player objects, and draft prospects don't have any stats
                delete p.stats;

                p = await player.updateValues(tx, p, []);
                await tx.players.put(p);
            });

            // "Top off" the draft class if <70 players imported
            if (players.length < 70) {
                await draft.genPlayers(tx, draftClassTid, scoutingRank, 70 - players.length);
            }
        });

        ui.realtimeUpdate(["dbChange"]);
    };
Esempio n. 8
0
        return team.getPayroll(null, inputs.tid).get(1).then(function (contracts) {
            var contractTotals, i, j, season, showInt;

            if (inputs.show === "all") {
                showInt = g.season - g.startingSeason + 1;
            } else {
                showInt = parseInt(inputs.show, 10);
            }

            // Convert contract objects into table rows
            contractTotals = [0, 0, 0, 0, 0];
            season = g.season;
            if (g.phase >= g.PHASE.DRAFT) {
                // After the draft, don't show old contract year
                season += 1;
            }
            for (i = 0; i < contracts.length; i++) {
                contracts[i].amounts = [];
                for (j = season; j <= contracts[i].exp; j++) {
                    // Only look at first 5 years (edited rosters might have longer contracts)
                    if (j - season >= 5) {
                        break;
                    }

                    contracts[i].amounts.push(contracts[i].amount / 1000);
                    contractTotals[j - season] += contracts[i].amount / 1000;
                }
                delete contracts[i].amount;
                delete contracts[i].exp;
            }

            vars.contracts = contracts;
            vars.contractTotals = contractTotals;
            vars.salariesSeasons = [season, season + 1, season + 2, season + 3, season + 4];

            return g.dbl.teamSeasons.index("tid, season").getAll(backboard.bound([inputs.tid], [inputs.tid, ''])).then(function (teamSeasons) {
                var barData, barSeasons, i, keys, tempData;

                teamSeasons.reverse(); // Most recent season first

                // Add in luxuryTaxShare if it's missing
                for (i = 0; i < teamSeasons.length; i++) {
                    if (!teamSeasons[i].revenues.hasOwnProperty("luxuryTaxShare")) {
                        teamSeasons[i].revenues.luxuryTaxShare = {
                            amount: 0,
                            rank: 15
                        };
                    }
                }

                keys = ["won", "hype", "pop", "att", "cash", "revenues", "expenses"];
                barData = {};
                for (i = 0; i < keys.length; i++) {
                    if (typeof teamSeasons[0][keys[i]] !== "object") {
                        barData[keys[i]] = helpers.nullPad(_.pluck(teamSeasons, keys[i]), showInt);
                    } else {
                        // Handle an object in the database
                        barData[keys[i]] = {};
                        tempData = _.pluck(teamSeasons, keys[i]);
                        _.each(tempData[0], function (value, key) {
                            barData[keys[i]][key] = helpers.nullPad(_.pluck(_.pluck(tempData, key), "amount"), showInt);
                        });
                    }
                }

                // Process some values
                barData.att = _.map(barData.att, function (num, i) {
                    if (teamSeasons[i] !== undefined) {
                        if (!teamSeasons[i].hasOwnProperty("gpHome")) { teamSeasons[i].gpHome = Math.round(teamSeasons[i].gp / 2); } // See also game.js and team.js
                        if (teamSeasons[i].gpHome > 0) {
                            return num / teamSeasons[i].gpHome; // per game
                        }
                        return 0;
                    }
                });
                keys = ["cash"];
                for (i = 0; i < keys.length; i++) {
                    barData[keys[i]] = _.map(barData[keys[i]], function (num) { return num / 1000; }); // convert to millions
                }

                barSeasons = [];
                for (i = 0; i < showInt; i++) {
                    barSeasons[i] = g.season - i;
                }

                vars.barData = barData;
                vars.barSeasons = barSeasons;
            });
        }).then(function () {
Esempio n. 9
0
function updateTeamHistory(inputs, updateEvents, vm) {
    if (updateEvents.indexOf("dbChange") >= 0 || updateEvents.indexOf("firstRun") >= 0 || updateEvents.indexOf("gameSim") >= 0 || inputs.abbrev !== vm.abbrev()) {
        return Promise.all([
            g.dbl.teamSeasons.index("tid, season").getAll(backboard.bound([inputs.tid], [inputs.tid, ''])),
            g.dbl.players.index('statsTids').getAll(inputs.tid).then(function (players) {
                return player.withStats(null, players, {
                    statsSeasons: "all",
                    statsTid: inputs.tid
                });
            })
        ]).spread(function (teamSeasons, players) {
            var bestRecord, championships, history, i, j, playoffAppearances, totalLost, totalWon, worstRecord;

            bestRecord = null;
            worstRecord = null;

            history = [];
            totalWon = 0;
            totalLost = 0;
            playoffAppearances = 0;
            championships = 0;
            for (i = 0; i < teamSeasons.length; i++) {
                history.push({
                    season: teamSeasons[i].season,
                    won: teamSeasons[i].won,
                    lost: teamSeasons[i].lost,
                    playoffRoundsWon: teamSeasons[i].playoffRoundsWon
                });
                totalWon += teamSeasons[i].won;
                totalLost += teamSeasons[i].lost;
                if (teamSeasons[i].playoffRoundsWon >= 0) {
                    playoffAppearances += 1;
                }
                if (teamSeasons[i].playoffRoundsWon === 4) {
                    championships += 1;
                }

                if (bestRecord === null || bestRecord.won < history[history.length - 1].won) {
                    bestRecord = history[history.length - 1];
                }
                if (worstRecord === null || worstRecord.lost < history[history.length - 1].lost) {
                    worstRecord = history[history.length - 1];
                }
            }
            history.reverse(); // Show most recent season first



            players = player.filter(players, {
                attrs: ["pid", "name", "injury", "tid", "hof", "watch"],
                ratings: ["pos"],
                stats: ["season", "abbrev", "gp", "min", "pts", "trb", "ast", "per", "ewa"],
                tid: inputs.tid
            });

            for (i = 0; i < players.length; i++) {
                players[i].stats.reverse();

                for (j = 0; j < players[i].stats.length; j++) {
                    if (players[i].stats[j].abbrev === g.teamAbbrevsCache[inputs.tid]) {
                        players[i].lastYr = players[i].stats[j].season + ' ';
                        break;
                    }
                }

                players[i].pos = players[i].ratings[players[i].ratings.length - 1].pos;

                delete players[i].ratings;
                delete players[i].stats;
            }

            return {
                abbrev: inputs.abbrev,
                history: history,
                players: players,
                team: {
                    name: g.teamNamesCache[inputs.tid],
                    region: g.teamRegionsCache[inputs.tid],
                    tid: inputs.tid
                },
                totalWon: totalWon,
                totalLost: totalLost,
                playoffAppearances: playoffAppearances,
                championships: championships,
                bestRecord: bestRecord,
                worstRecord: worstRecord
            };
        });
    }
}
Esempio n. 10
0
 }).then(function () {
     return tx.teamSeasons.index("season, tid").getAll(backboard.bound([g.season - 1], [g.season - 1, ''])).map(function (teamSeason) {
         return teamSeason.expenses.coaching.rank;
     });
 }).then(function (coachingRanks) {
Esempio n. 11
0
    }).then(function (teams) {
        var cid, i, series, teamsConf, tidPlayoffs;

        // Add entry for wins for each team; delete winp, which was only needed for sorting
        for (i = 0; i < teams.length; i++) {
            teams[i].won = 0;
        }

        if (!localStorage.top16playoffs) {
            // Default: top 8 teams in each conference
            tidPlayoffs = [];
            series = [[], [], [], []];  // First round, second round, third round, fourth round
            for (cid = 0; cid < 2; cid++) {
                teamsConf = [];
                for (i = 0; i < teams.length; i++) {
                    if (teams[i].cid === cid) {
                        if (teamsConf.length < 8) {
                            teamsConf.push(teams[i]);
                            tidPlayoffs.push(teams[i].tid);
                        }
                    }
                }
                series[0][cid * 4] = {home: teamsConf[0], away: teamsConf[7]};
                series[0][cid * 4].home.seed = 1;
                series[0][cid * 4].away.seed = 8;
                series[0][1 + cid * 4] = {home: teamsConf[3], away: teamsConf[4]};
                series[0][1 + cid * 4].home.seed = 4;
                series[0][1 + cid * 4].away.seed = 5;
                series[0][2 + cid * 4] = {home: teamsConf[2], away: teamsConf[5]};
                series[0][2 + cid * 4].home.seed = 3;
                series[0][2 + cid * 4].away.seed = 6;
                series[0][3 + cid * 4] = {home: teamsConf[1], away: teamsConf[6]};
                series[0][3 + cid * 4].home.seed = 2;
                series[0][3 + cid * 4].away.seed = 7;
            }
        } else {
            // Alternative (localStorage.top16playoffs): top 16 teams overall
            tidPlayoffs = [];
            series = [[], [], [], []];  // First round, second round, third round, fourth round
            teamsConf = [];
            for (i = 0; i < teams.length; i++) {
                if (teamsConf.length < 16) {
                    teamsConf.push(teams[i]);
                    tidPlayoffs.push(teams[i].tid);
                }
            }
            series[0][0] = {home: teamsConf[0], away: teamsConf[15]};
            series[0][0].home.seed = 1;
            series[0][0].away.seed = 16;
            series[0][1] = {home: teamsConf[7], away: teamsConf[8]};
            series[0][1].home.seed = 8;
            series[0][1].away.seed = 9;
            series[0][2] = {home: teamsConf[3], away: teamsConf[12]};
            series[0][2].home.seed = 4;
            series[0][2].away.seed = 13;
            series[0][3] = {home: teamsConf[4], away: teamsConf[11]};
            series[0][3].home.seed = 5;
            series[0][3].away.seed = 12;
            series[0][4] = {home: teamsConf[1], away: teamsConf[14]};
            series[0][4].home.seed = 2;
            series[0][4].away.seed = 15;
            series[0][5] = {home: teamsConf[6], away: teamsConf[9]};
            series[0][5].home.seed = 7;
            series[0][5].away.seed = 10;
            series[0][6] = {home: teamsConf[2], away: teamsConf[13]};
            series[0][6].home.seed = 3;
            series[0][6].away.seed = 14;
            series[0][7] = {home: teamsConf[5], away: teamsConf[10]};
            series[0][7].home.seed = 6;
            series[0][7].away.seed = 11;
        }

        tidPlayoffs.forEach(function (tid) {
            eventLog.add(null, {
                type: "playoffs",
                text: 'The <a href="' + helpers.leagueUrl(["roster", g.teamAbbrevsCache[tid], g.season]) + '">' + g.teamNamesCache[tid] + '</a> made the <a href="' + helpers.leagueUrl(["playoffs", g.season]) + '">playoffs</a>.',
                showNotification: tid === g.userTid,
                tids: [tid]
            });
        });

        return Promise.all([
            tx.playoffSeries.put({
                season: g.season,
                currentRound: 0,
                series: series
            }),

            // Add row to team stats and team season attributes
            tx.teamSeasons.index("season, tid").iterate(backboard.bound([g.season], [g.season, '']), function (teamSeason) {
                if (tidPlayoffs.indexOf(teamSeason.tid) >= 0) {
                    tx.teamStats.add(team.genStatsRow(teamSeason.tid, true));

                    teamSeason.playoffRoundsWon = 0;

                    // More hype for making the playoffs
                    teamSeason.hype += 0.05;
                    if (teamSeason.hype > 1) {
                        teamSeason.hype = 1;
                    }
                } else {
                    // Less hype for missing the playoffs
                    teamSeason.hype -= 0.05;
                    if (teamSeason.hype < 0) {
                        teamSeason.hype = 0;
                    }
                }

                return teamSeason;
            }),

            // Add row to player stats
            Promise.map(tidPlayoffs, function (tid) {
                return tx.players.index('tid').iterate(tid, function (p) {
                    return player.addStatsRow(tx, p, true);
                });
            })
        ]);
    }).then(function () {