BGA Pythia - 7 Wonders Architects game helper

Visual aid that extends BGA game interface with useful information

目前为 2021-12-30 提交的版本。查看 最新版本

// ==UserScript==
// @name         BGA Pythia - 7 Wonders Architects game helper
// @description  Visual aid that extends BGA game interface with useful information
// @namespace    https://github.com/dpavliuchkov/bga-pythia
// @author       https://github.com/dpavliuchkov
// @version      1.2
// @license      MIT
// @include      *boardgamearena.com/*
// @grant        none
// ==/UserScript==

// System variables - don't edit
const Enable_Logging = false;
const Is_Inside_Game = /\?table=[0-9]*/.test(window.location.href);
const BGA_Player_Scoreboard_Id_Prefix = "overall_player_board_";
const BGA_Player_Score_Right_Id_Prefix = "player_name_";
const BGA_Player_Score_Main_Id_Prefix = "playerarea_";
const BGA_Progress_Id_Prefix = "pg_";
const Player_Score_Right_Id_Prefix = "pythia_score_right_";
const Player_Score_Main_Id_Prefix = "pythia_score_main_";
const Player_Score_Span_Class = "player_score_value";
const Player_Leader_Class = "pythia_leader";
const Progress_Worth_Class = "progress_worth";
const Cat_Card_Type_Id = 16;
const Politics_Progress_Type_Id = 2;
const Decor_Progress_Type_Id = 6;
const Strategy_Progress_Type_Id = 8;
const Education_Progress_Type_Id = 12;
const Culture_Progress_Type_Id = 13;
const Decor_Points = 4;

// progress tokens - type args
// 1 - science draw
// 2 - cat politics
// 3 - wood / clay
// 4 - ??
// 5 - double gold
// 6 - decor
// 7 - wonder stages
// 8 - victories
// 9 - two shields
// 10 - horns
// 11 - ??
// 12 - education
// 13 - culture
// 14 - engineering
// 15 - ??
// 16 - stone gold
// 17 - cat pawn


// Main Pythia object
var pythia = {
    isStarted: false,
    isFinished: false,
    dojo: null,
    game: null,
    mainPlayerId: null,
    players: [],

    // Init Pythia
    init: function() {
        this.isStarted = true;
        // Check if the site was loaded correctly
        if (!window.parent || !window.parent.dojo || !window.parent.gameui.gamedatas) {
            return;
        }
        this.dojo = window.parent.dojo;
        this.game = window.parent.gameui.gamedatas;

        const playerOrder = this.game.playerorder;
        this.playersCount = playerOrder.length;
        this.mainPlayerId = playerOrder[0];

        // Prepare player objects and containers
        const keys = Object.keys(this.game.players);
        for (const playerId of keys) {
            this.players[playerId] = {
                score: 0,
                wonderStages: 0,
                totalCatCards: 0,
                totalWarVictories: 0,
                totalProgressTokens: 0,
                hasDecor: false,
                hasCulture: false,
            };
            this.renderPythiaContainers(playerId);
        }

        // Prepare progress tokens
        this.progressTokens = {};
        this.initProgressTokens();

        this.setStyles();

        // Connect event handlers to follow game progress
        this.dojo.subscribe("updateScore", this, "recordScoreUpdate");
        this.dojo.subscribe("getProgress", this, "recordProgressToken");
        this.dojo.subscribe("getCard", this, "recordGetCard");
        this.dojo.subscribe("flipWonderStage", this, "recordWonderStage");
        this.dojo.subscribe("conflictResult", this, "recordWarResult");
        this.dojo.subscribe("showProgress", this, "recordProgressShow");
        this.dojo.subscribe("victory", this, "recordVictory");

        if (Enable_Logging) console.log("PYTHIA: My eyes can see everything!");
        return this;
    },

    // Record new scores
    recordScoreUpdate: function(data) {
        if (Enable_Logging) console.log("PYTHIA: scores updated - I got", data);

        // Input check
        if (!data || !data.args) {
            return;
        }

        const playerId = data.args.player_id;
        const score = data.args.score;
        this.players[playerId].score = score;
        this.renderPlayerScore(playerId, score);
        this.renderLeader();
    },

    // Record which card a player got
    recordGetCard: function(data) {
        if (Enable_Logging) console.log("PYTHIA: player took a card - I got", data);

        // Input check
        if (!data || !data.args || !data.args.card) {
            return;
        }

        const playerId = data.args.player_id;
        var playerObjectChanged = false;

        // Increment if player drew a cat card
        if (data.args.card.type == Cat_Card_Type_Id) {
            this.players[playerId].totalCatCards++;
            playerObjectChanged = true;
        }

        // Update score values for progress tokens
        if (playerObjectChanged) {
            this.renderProgressWorth();
        }
    },

    // Record when a wonder stage was built
    recordWonderStage: function(data) {
        if (Enable_Logging) console.log("PYTHIA: wonder stage built - I got", data);

        // Input check
        if (!data || !data.args || !data.args.player_id) {
            return;
        }

        const playerId = data.args.player_id;
        this.players[playerId].wonderStages++; // increase a counter of built wonder stages
    },

    // Record which progress a player got
    recordProgressToken: function(data) {
        if (Enable_Logging) console.log("PYTHIA: player took a progress token - I got", data);

        // Input check
        if (!data || !data.args || !data.args.progress) {
            return;
        }

        const playerId = data.args.player_id;
        const token = data.args.progress;

        // Track progress tokens that can give victory points
        if (token.type_arg == Decor_Progress_Type_Id) {
            this.players[playerId].hasDecor = true;
        }
        if (token.type_arg == Culture_Progress_Type_Id) {
            this.players[playerId].hasCulture = true;
        }

        // Increment total progress tokens
        this.players[playerId].totalProgressTokens++;

        // Remove this token from the open list
        delete this.progressTokens[token.id];
    },

    // Record war results
    recordWarResult: function(data) {
        if (Enable_Logging) console.log("PYTHIA: war has ended - I got", data);

        // Input check
        if (!data || !data.args || !data.args.score) {
            return;
        }

        // Update who got military win tokens
        const warResults = data.args.score;
        for (const playerId in warResults) {
            this.players[playerId].totalWarVictories += warResults[playerId].length;
        }

        // Update score values for progress tokens
        this.renderProgressWorth();
    },

    // Record which new progress token was shown
    recordProgressShow: function(data) {
        if (Enable_Logging) console.log("PYTHIA: new progress token displayed - I got", data);

        // Input check
        if (!data || !data.args || !data.args.progress) {
            return;
        }

        // Add this token to the open list
        const token = data.args.progress;
        this.progressTokens[token.id] = token.type_arg;

        // Update score values for progress tokens
        this.renderProgressWorth();
    },

    // Record that the game has ended
    recordVictory: function(data) {
        if (Enable_Logging) console.log("PYTHIA: game has finished - I got", data);
        this.isFinished = true;
    },

    renderProgressWorth: function() {
        const mainPlayer = this.players[this.mainPlayerId];

        // Clean previous values
        this.dojo.query("." + Progress_Worth_Class).forEach(this.dojo.destroy);

        // Calculate progress worth in points
        for (var i in this.progressTokens) {
            const token = this.progressTokens[i];
            var pointsWorth = 0;

            switch (token) {
                case Politics_Progress_Type_Id:
                    pointsWorth = mainPlayer.totalCatCards;
                    break;

                case Decor_Progress_Type_Id:
                    pointsWorth = Decor_Points;
                    break;

                case Strategy_Progress_Type_Id:
                    pointsWorth = mainPlayer.totalWarVictories;
                    break;

                case Education_Progress_Type_Id:
                    pointsWorth = mainPlayer.totalProgressTokens * 2;
                    break;

                case Culture_Progress_Type_Id:
                    pointsWorth = 4;
                    if (mainPlayer.hasCulture) {
                        pointsWorth = 8;
                    }
                    break;
            }

            // Render progress worth
            var progressToken = this.dojo.byId(BGA_Progress_Id_Prefix + i);
            if (progressToken && !this.isFinished) {
                this.dojo.place(
                    "<span class='" + Progress_Worth_Class + "'>⭐" + pointsWorth + "</span>",
                    progressToken,
                    "first");
            }
        }
    },

    // Update total player score
    renderPlayerScore: function(playerId, score = 0) {
        var playerScore = this.dojo.byId(Player_Score_Main_Id_Prefix + playerId);
        if (playerScore) {
            if (!this.isFinished) {
                if (this.players[playerId].hasDecor) {
                    score += Decor_Points;
                }
            }
            this.dojo.query("#" + Player_Score_Main_Id_Prefix + playerId)[0]
                .innerHTML = "⭐" + score;
            this.dojo.query("#" + Player_Score_Right_Id_Prefix + playerId)[0]
                .innerHTML = "⭐" + score;
        }
    },

    // Add border and position of the leader player
    renderLeader: function() {
        // Clean previous leader
        this.dojo.query("." + Player_Leader_Class).removeClass(Player_Leader_Class);

        // Find leader
        var totalScores = [];
        const keys = Object.keys(this.players);
        for (const playerId of keys) {
            totalScores.push(
                [playerId,
                    this.players[playerId].score,
                    this.players[playerId].wonderStages
                ]);
        }
        totalScores.sort(function(a, b) {
            return b[1] - a[1] || b[2] - a[2];;
        });

        // Mark new leader
        this.dojo.addClass(BGA_Player_Scoreboard_Id_Prefix + totalScores[0][0], Player_Leader_Class);
        this.dojo.addClass(BGA_Player_Score_Main_Id_Prefix + totalScores[0][0], Player_Leader_Class);
    },

    // Render player containers
    renderPythiaContainers: function(playerId) {
        // Insert war score container in scores table
        if (!this.dojo.byId(Player_Score_Main_Id_Prefix + playerId)) {
            const mainPlayerArea = this.dojo.query("#" + BGA_Player_Score_Main_Id_Prefix + playerId + " .stw_name_holder");
            this.dojo.place(
                "<span id='" + Player_Score_Main_Id_Prefix + playerId + "'" +
                "class='" + Player_Score_Span_Class + "'>⭐0</span>",
                mainPlayerArea[0],
                "first");

            this.dojo.place(
                "<span id='" + Player_Score_Right_Id_Prefix + playerId + "'" +
                "class='" + Player_Score_Span_Class + "'>⭐0</span>",
                BGA_Player_Score_Right_Id_Prefix + playerId,
                "first");
        }
    },

    initProgressTokens: function() {
        const tokens = this.dojo.query("#science .progress");
        for (var i in tokens) {
            const token = tokens[i];
            if (!token.style || !token.id) {
                continue;
            }

            const tokenId = parseInt(token.id.substr(3));
            if (tokenId == 0) {
                continue;
            }

            // Calculate token background - to find which token is displayed
            const posX = Math.abs(parseInt(token.style.backgroundPositionX) / 100);
            const posY = Math.abs(parseInt(token.style.backgroundPositionY) / 100);

            // Relevant tokens
            if (posY == 0 && posX == 2) {
                this.progressTokens[tokenId] = Politics_Progress_Type_Id;
                continue;
            }
            if (posY == 1 && posX == 2) {
                this.progressTokens[tokenId] = Decor_Progress_Type_Id;
                continue;
            }
            if (posY == 2 && posX == 0) {
                this.progressTokens[tokenId] = Strategy_Progress_Type_Id;
                continue;
            }
            if (posY == 3 && posX == 0) {
                this.progressTokens[tokenId] = Education_Progress_Type_Id;
                continue;
            }
            if (posY == 3 && posX == 1) {
                this.progressTokens[tokenId] = Culture_Progress_Type_Id;
                continue;
            }
            // Not relevant token
            this.progressTokens[tokenId] = 0;
        }

        this.renderProgressWorth();
    },

    // Set Pythia CSS styles
    setStyles: function() {
        this.dojo.query("body").addClass("pythia_enabled");
        this.dojo.place(
            "<style type='text/css' id='Pythia_Styles'>" +
            "." + Player_Leader_Class + " .player_board_inner { border: 3px solid; border-color: green; border-radius: 10px; } " +
            "." + Player_Leader_Class + " .stw_name_holder { background-color: green; border-radius: 5px; color: white; } " +
            ".stw_name_holder ." + Player_Score_Span_Class + " { margin-right: 10px; margin-top: -6px; } " +
            "#science ." + Progress_Worth_Class + "  { display: block; position: relative; top: -32px; left: 25%; height: 25px; width: 34px; background: #F5EDE6; padding-left: 4px; padding-bottom: 3px; border-radius: 10px; } " +
            "</style>", "game_play_area_wrap", "last");
    }
};

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

function isObjectEmpty(object) {
    return typeof(object) == "undefined" ||
        (Object.keys(object).length === 0 && object.constructor === Object);
}

// Everything starts here
window.onload = async function() {
    if (Is_Inside_Game) {
        await sleep(3000); // Wait for BGA to load dojo and 7W scripts
        if (!window.parent || !window.parent.gameui || !window.parent.gameui.game_name ||
            window.parent.gameui.game_name != "sevenwondersarchitects") {
            return;
        }

        // Prevent multiple launches
        if (window.parent.isPythiaStarted) {
            return;
        } else {
            if (Enable_Logging) console.log("PYTHIA: I have come to serve you");
            window.parent.isPythiaStarted = true;
            window.parent.pythia = pythia.init();
        }
    }
};