- // ==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.6
- // @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 Progress_Worth_Id_Prefix = "pythia_progress_worth_";
- 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 Cat_Pawn_Type_Id = 17;
- 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
-
- // big game https://boardgamearena.com/1/sevenwondersarchitects?table=227901083
-
-
- // 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] = {
- bgaScore: 0,
- wonderStages: 0,
- totalCatCards: 0,
- totalWarVictories: 0,
- totalProgressTokens: 0,
- hasDecor: false,
- hasCulture: false,
- hasEducation: 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;
- this.players[playerId].bgaScore = data.args.score;
-
- const totalScore = this.getPlayerScore(playerId);
- this.renderPlayerScore(playerId, totalScore);
-
- const leaderId = this.getLeader();
- this.renderLeader(leaderId);
- },
-
- // 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 && playerId == this.mainPlayerId) {
- this.updateAllProgressWorth();
- }
- },
-
- // 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
-
- // 5th wonder stage means the game is over
- if (this.players[playerId].wonderStages == 5) {
- this.isFinished = true;
- }
- },
-
- // 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;
- }
-
- // Skip movements of the cat pawn
- if (data.args.progress.type_arg == Cat_Pawn_Type_Id) {
- 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;
- }
- if (token.type_arg == Education_Progress_Type_Id) {
- this.players[playerId].hasEducation = true;
- }
-
- // Increment total progress tokens
- this.players[playerId].totalProgressTokens++;
-
- // Remove this token from the open list
- delete this.progressTokens[token.id];
-
- // Remove progress worth container
- this.dojo.destroy(Progress_Worth_Id_Prefix + token.id);
-
- // Update score values for progress tokens
- if (playerId == this.mainPlayerId) {
- this.updateAllProgressWorth();
- }
- },
-
- // 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.updateAllProgressWorth();
- },
-
- // 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
- const progressWorth = this.getProgressWorth(token.type_arg, this.mainPlayerId);
- this.renderProgressWorth(token.id, progressWorth);
- },
-
- // Record that the game has ended
- recordVictory: function(data) {
- if (Enable_Logging) console.log("PYTHIA: game has finished - I got", data);
-
- // Input check
- if (!data || !data.args || !data.args.score) {
- return;
- }
-
- this.isFinished = true;
- const finalScores = data.args.score;
-
- const keys = Object.keys(finalScores);
- for (const playerId of keys) {
- this.renderPlayerScore(playerId, finalScores[playerId].total);
- }
-
- const leaderId = this.getLeader();
- this.renderLeader(leaderId);
- },
-
- renderProgressWorth: function(progressId, worth = 0) {
- // Clean previous value
- this.dojo.destroy(Progress_Worth_Id_Prefix + progressId);
-
- // Render progress worth
- this.dojo.place(
- "<span id='" + Progress_Worth_Id_Prefix + progressId + "'" +
- " class='" + Progress_Worth_Class + "'>⭐" + worth + "</span>",
- BGA_Progress_Id_Prefix + progressId,
- "first");
- },
-
- // Update total player score
- renderPlayerScore: function(playerId, score = 0) {
- var playerScore = this.dojo.byId(Player_Score_Main_Id_Prefix + playerId);
- if (playerScore) {
- 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(leaderId) {
- // Clean previous leader
- this.dojo.query("." + Player_Leader_Class).removeClass(Player_Leader_Class);
-
- // Mark new leader
- this.dojo.addClass(BGA_Player_Scoreboard_Id_Prefix + leaderId, Player_Leader_Class);
- this.dojo.addClass(BGA_Player_Score_Main_Id_Prefix + leaderId, 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");
- }
- },
-
- // Called at the game start to detect which progress tokens were drawn
- initProgressTokens: function() {
- const tokens = this.dojo.query("#science .progress");
- for (const token of tokens) {
- 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);
-
- var tokenType;
- // Relevant tokens
- if (posY == 0 && posX == 2) {
- tokenType = Politics_Progress_Type_Id;
- } else if (posY == 1 && posX == 2) {
- tokenType = Decor_Progress_Type_Id;
- } else if (posY == 2 && posX == 0) {
- tokenType = Strategy_Progress_Type_Id;
- } else if (posY == 3 && posX == 0) {
- tokenType = Education_Progress_Type_Id;
- } else if (posY == 3 && posX == 1) {
- tokenType = Culture_Progress_Type_Id;
- } else {
- // Not relevant token
- tokenType = 0;
- }
-
- this.progressTokens[tokenId] = tokenType;
- const progressWorth = this.getProgressWorth(tokenType, this.mainPlayerId);
- this.renderProgressWorth(tokenId, progressWorth);
- }
- },
-
- // Update worth of all visible progress tokens
- updateAllProgressWorth: function() {
- for (var tokenId in this.progressTokens) {
- const tokenType = this.progressTokens[tokenId];
- const progressWorth = this.getProgressWorth(tokenType, this.mainPlayerId);
- this.renderProgressWorth(tokenId, progressWorth);
- }
- },
-
- // Calculate how much this progress is worth for this player
- getProgressWorth: function(tokenType, playerId) {
- const player = this.players[playerId];
- var pointsWorth = 0;
-
- switch (parseInt(tokenType)) {
- case Politics_Progress_Type_Id:
- pointsWorth = player.totalCatCards;
- break;
-
- case Decor_Progress_Type_Id:
- pointsWorth = Decor_Points;
- break;
-
- case Strategy_Progress_Type_Id:
- pointsWorth = player.totalWarVictories;
- break;
-
- case Education_Progress_Type_Id:
- pointsWorth = 2 + player.totalProgressTokens * 2;
- break;
-
- case Culture_Progress_Type_Id:
- pointsWorth = 4;
- if (player.hasCulture) {
- pointsWorth = 8;
- }
- break;
- }
-
- if (player.hasEducation) {
- pointsWorth += 2;
- }
-
- return pointsWorth;
- },
-
- // Get total player score at teh current moment of game
- getPlayerScore: function(playerId) {
- const player = this.players[playerId];
- var totalScore = player.bgaScore;
-
- // If game not finished - add 4 points for decor progress
- if (!this.isFinished && player.hasDecor) {
- totalScore += Decor_Points;
- }
- return totalScore;
- },
-
- // Calculate who is the current leader
- getLeader: function() {
- // Find leader
- var totalScores = [];
- const keys = Object.keys(this.players);
- for (const playerId of keys) {
- totalScores.push(
- [playerId,
- this.getPlayerScore(playerId),
- this.players[playerId].wonderStages
- ]);
- }
- totalScores.sort(function(a, b) {
- return b[1] - a[1] || b[2] - a[2];
- });
-
- return totalScores[0][0];
- },
-
- // 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-name" +
- ", ." + Player_Leader_Class + " a" +
- ", ." + Player_Leader_Class + " .stw_name_holder" +
- " { background-color: green; border-radius: 5px; color: white ! important; } " +
- ".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
- var 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();
- }
- }
- };
-
- if (document.readyState === "complete") {
- onload();
- } else {
- (addEventListener || attachEvent).call(window, addEventListener ? "load" : "onload", onload);
- }