您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Visual aid that shows which cards each player holds, and how war affects the scores
当前为
// ==UserScript== // @name BGA Pythia - 7 Wonders game helper // @description Visual aid that shows which cards each player holds, and how war affects the scores // @namespace https://github.com/dpavliuchkov/bga-pythia // @author https://github.com/dpavliuchkov // @version 0.4 // @include *boardgamearena.com/* // @grant none // ==/UserScript== // // On boardgamearena.com, you can play an exciting board game of 7 wonders. // However, it is hard to remember which cards each player has. Pythia has // godlike powers and will share this information with you. It will also // display total player's score based on the current shields situation. // And it will mark leader and runner up players and their boards. // Works with Tampermonkey only. // ==/UserScript== // System variables - don't edit const Is_Inside_Game = /\?table=[0-9]*/.test(window.location.href); const Cards_Image = 'https://x.boardgamearena.net/data/themereleases/current/games/sevenwonders/200213-1215/img/cards.jpg'; const BGA_Player_Board_Id_Prefix = 'player_board_wrap_'; const BGA_Player_Score_Id_Prefix = 'player_score_'; const Player_Cards_Id_Prefix = 'pythia_cards_wrap_'; const Player_Score_Id_Prefix = 'pythia_score_'; const Player_Cards_Div_Class = 'pythia_cards_container'; const Player_Score_Span_Class = 'pythia_score'; const Player_Leader_Class = 'pythia_leader'; const Player_Runnerup_Class = 'pythia_runnerup'; const War_Points_Per_Age = { 1: 1, 2: 3, 3: 5 }; const Enable_Logging = false; // Styling variables - feel free to customize const CSS_Player_Cards_Div_Height = '50px'; const CSS_Player_Card_Zoom = 0.6; const CSS_Player_Card_Height = '45px'; const CSS_Player_Card_Width = '128px'; const CSS_Player_Card_Title_Top = '-25px'; const CSS_Player_Card_Title_Font_Size = '18px'; const CSS_Player_Card_Title_Font_Color = 'black'; // Main Pythia object var pythia = { isStarted: false, dojo: null, game: null, mainPlayer: null, currentAge: 1, playersCount: 0, players: [], // Init Pythia init: function() { this.isStarted = true; this.dojo = window.parent.dojo; this.game = window.parent.gameui.gamedatas; var playerOrder = this.game.playerorder; this.playersCount = playerOrder.length; this.mainPlayer = playerOrder[0]; for (var i = 0; i < this.playersCount; i++) { var playerId = playerOrder[i]; this.players[playerId] = { hand: {}, coins: 3, shields: 0, bgaScore: 1, warScore: 0, wonder: 0 }; // Identify who sits to the left and to the right if (playerId == this.mainPlayer) { this.players[playerId].left = playerOrder[this.playersCount - 1]; } else { this.players[playerId].left = playerOrder[i - 1]; } if (playerId == playerOrder[this.playersCount - 1]) { this.players[playerId].right = this.mainPlayer; } else { this.players[playerId].right = playerOrder[i + 1]; } this.renderPythiaContainers(playerId); } this.setStyles(); this.dojo.subscribe("newHand", this, "recordHand"); this.dojo.subscribe("cardsPlayed", this, "recordTurn"); this.dojo.subscribe("coinDelta", this, "recordCoins"); this.dojo.subscribe("discard", this, "recordDiscard"); this.dojo.subscribe("newWonders", this, "recordWonderChoice"); this.dojo.subscribe("wonderBuild", this, "recordWonderStage"); this.dojo.subscribe("updateScore", this, "recordScoreUpdate"); this.dojo.subscribe("warVictory", this, "recordWarResults"); this.dojo.subscribe("newAge", this, "changeAge"); if (Enable_Logging) console.log("PYTHIA: My eyes can see everything!"); return this; }, // Check what came to main player in the new hand recordHand: function(data) { if (Enable_Logging) console.log("PYTHIA: new hand - I got", data); // Rotate old hands and render cards if (!this.isFirstTurn()) { this.passCards(); this.renderPlayerCards(); } // Save new hand to main player this.players[this.mainPlayer].hand = data.args.cards; // Update leader & runnerup positions this.renderLeaderRunnerup(); }, // Process all cards played by all players recordTurn: function(data) { if (Enable_Logging) console.log("PYTHIA: cards played - I got", data); var warPlayed = false; // Cycle all played cards for (var cardId in data.args.cards) { var card = data.args.cards[cardId]; var player = card.location_arg; // Track if played card was military if (this.game.card_types[card.type].category == "mil") { warPlayed = true; this.players[player].shields += this.game.card_types[card.type].shield; } // Delete played card if (isObjectEmpty(this.players[player].hand)) { continue; } delete this.players[player].hand[cardId]; } if (warPlayed) { this.calculateWarScores(); } }, recordCoins: function(data) { if (Enable_Logging) console.log("PYTHIA: coins changed - I got", data); // debugger; this.players[data.args.player_id].coins += data.args.coinddelta; }, // If main player discarded - we know what card it was recordDiscard: function(data) { if (Enable_Logging) console.log("PYTHIA: card discarded - I got", data); var player = data.channelorig.substring(9); delete this.players[player].hand[data.args.card_id]; }, // If Rhodos built a stage - it could have shields recordWonderStage: function(data) { if (Enable_Logging) console.log("PYTHIA: wonder built - I got", data); const playerId = data.args.player_id; const stage = data.args.step; const wonderId = this.players[playerId].wonder; if (this.game.wonders[wonderId].stages[stage].shield) { this.players[playerId].shields += this.game.wonders[wonderId].stages[stage].shield; this.calculateWarScores(); } }, // Record which wonder each player has chosen recordWonderChoice : function(data) { if (Enable_Logging) console.log("PYTHIA: wonders chosen - I got", data); const wonders = Object.keys(data.args.wonders); for (const playerId of wonders) { this.players[playerId].wonder = data.args.wonders[playerId]; } }, // Update internal scores as well recordScoreUpdate: function(data) { if (Enable_Logging) console.log("PYTHIA: scores updated - I got", data); const scores = Object.keys(data.args.scores); for (const playerId of scores) { this.players[playerId].bgaScore = data.args.scores[playerId]; this.renderPlayerScore(playerId); } }, // If this is the last war - do cleanup recordWarResults: function(data) { if (Enable_Logging) console.log("PYTHIA: war battle happened - I got", data); if (this.currentAge == 3) { // Hide Pythia scores this.dojo.query('.' + Player_Score_Span_Class).style('display', 'none'); // Remove Pythia leader & runnerup notation if (this.dojo.query('.' + Player_Leader_Class)[0]) { this.dojo.removeClass(this.dojo.query('.' + Player_Leader_Class)[0].id, Player_Leader_Class); } if (this.dojo.query('.' + Player_Runnerup_Class)[0]) { this.dojo.removeClass(this.dojo.query('.' + Player_Runnerup_Class)[0].id, Player_Runnerup_Class); } } }, // Calculate additional score from shields calculateWarScores: function() { var currentPlayerId = this.mainPlayer; var i = 0; while (i < this.playersCount) { var thisPlayer = this.players[currentPlayerId]; thisPlayer.warScore = 0; // Check battles with right neighbour var rightPlayer = this.players[thisPlayer.right]; if (thisPlayer.shields > rightPlayer.shields) { this.increaseWarScore(currentPlayerId, this.currentAge); } else if (thisPlayer.shields < rightPlayer.shields) { this.decreaseWarScore(currentPlayerId, this.currentAge); } // Check battles with left neighbour var leftPlayer = this.players[thisPlayer.left]; if (thisPlayer.shields > leftPlayer.shields) { this.increaseWarScore(currentPlayerId, this.currentAge); } else if (thisPlayer.shields < leftPlayer.shields) { this.decreaseWarScore(currentPlayerId, this.currentAge); } currentPlayerId = thisPlayer.right; i++; } }, // Cleanup things between ages changeAge: function(data) { if (Enable_Logging) console.log("PYTHIA: new age - I got", data); this.currentAge++; // Recalculate war scores for the new age this.calculateWarScores(); const keys = Object.keys(this.players); for (const playerId of keys) { // Clean player hands and update total scores this.players[playerId].hand = {}; this.renderPlayerScore(playerId); this.renderLeaderRunnerup(); } // Clean rendered cards from previous age this.dojo.query('.' + Player_Cards_Div_Class).forEach(this.dojo.empty); }, // Add war scores based on the age increaseWarScore: function(playerId, age) { this.players[playerId].warScore += War_Points_Per_Age[age]; }, // Decrase war scores decreaseWarScore: function(playerId, age) { this.players[playerId].warScore -= 1; }, // Move cards unplayed cards between players passCards: function() { // This should be counter to age direction, because // Pythia always passes starting from the last player var direction = this.currentAge == 2 ? 'right' : 'left'; var currentPlayerId = this.mainPlayer; var i = 0; while (i < this.playersCount) { var neighborId = this.players[currentPlayerId][direction]; this.players[neighborId].hand = this.players[this.players[neighborId][direction]].hand; currentPlayerId = neighborId; i++; } }, // Render player containers renderPythiaContainers: function(playerId) { // Insert war score container if (!this.dojo.byId(Player_Score_Id_Prefix + playerId)) { this.dojo.place( '<span id="' + Player_Score_Id_Prefix + playerId + '"' + 'class="player_score_value ' + Player_Score_Span_Class + '"></span>', BGA_Player_Score_Id_Prefix + playerId, 'after'); } // Insert card container if (playerId == this.mainPlayer || this.dojo.byId(Player_Cards_Id_Prefix + playerId)) { return; } this.dojo.place('<div id="' + Player_Cards_Id_Prefix + playerId + '"' + ' class="' + Player_Cards_Div_Class + '"' + ' style="height: ' + CSS_Player_Cards_Div_Height + ';"></div>', BGA_Player_Board_Id_Prefix + playerId, 'first'); }, // Render player hands renderPlayerCards: function() { const keys = Object.keys(this.players); for (const playerId of keys) { if (playerId == this.mainPlayer || isObjectEmpty(this.players[playerId].hand)) { continue; } var cardsHTML = ''; var left = 7; for (var card in this.players[playerId].hand) { var playedCard = this.game.card_types[this.players[playerId].hand[card].type]; var posX = -playedCard.backx; var posY = -playedCard.backy; cardsHTML += '<div class="stockitem stockitem_unselectable"' + 'style="zoom: ' + CSS_Player_Card_Zoom + '; background-position: ' + posX + 'px ' + posY + 'px;' + 'top: 25px; left: ' + left + 'px; width: ' + CSS_Player_Card_Width + '; height: ' + CSS_Player_Card_Height + ';' + ' background-image: url(' + Cards_Image + '); opacity: 1; border-width: 0px;">'; cardsHTML += '<span style="position: absolute; top: ' + CSS_Player_Card_Title_Top + '; font-size: ' + CSS_Player_Card_Title_Font_Size + '; color: ' + CSS_Player_Card_Title_Font_Color + ';">' + playedCard.nametr + '</span></div>'; left += parseInt(CSS_Player_Card_Width) + 2; } this.dojo.place(cardsHTML, Player_Cards_Id_Prefix + playerId, "only"); } }, // Update total player score renderPlayerScore: function(playerId, score = 0) { const totalScore = this.players[playerId].bgaScore + this.players[playerId].warScore; this.dojo.byId(Player_Score_Id_Prefix + playerId).innerHTML = " (" + totalScore + ")"; }, renderLeaderRunnerup: function() { // Clean previous leader & runnerup - fucked up way, but no idea how to make it nicer if (this.dojo.query('.' + Player_Leader_Class)[0]) { this.dojo.removeClass(this.dojo.query('.' + Player_Leader_Class)[0].id, Player_Leader_Class); } if (this.dojo.query('.' + Player_Runnerup_Class)[0]) { this.dojo.removeClass(this.dojo.query('.' + Player_Runnerup_Class)[0].id, Player_Runnerup_Class); } // Find leader and runner ups var totalScores = []; const keys = Object.keys(this.players); for (const playerId of keys) { totalScores.push( [playerId, this.players[playerId].bgaScore + this.players[playerId].warScore, this.players[playerId].coins ]); } totalScores.sort(function(a, b) { return b[1] - a[1] || b[2] - a[2];; }); // Mark new ones this.dojo.addClass(BGA_Player_Board_Id_Prefix + totalScores[0][0], Player_Leader_Class); this.dojo.addClass(BGA_Player_Board_Id_Prefix + totalScores[1][0], Player_Runnerup_Class); }, // Is this the first turn of the age? isFirstTurn: function() { return isObjectEmpty(this.players[this.mainPlayer].hand); }, setStyles: function() { this.dojo.place( '<style type="text/css" id="Pythia_Styles">' + '.sw_coins { top: 50px; } ' + '#player_board_wrap_' + this.mainPlayer + ' .sw_coins { top: 0px; } ' + '.' + Player_Leader_Class + " { border: 5px solid green; } " + '.' + Player_Leader_Class + " h3::before { content: '(Leader) '; color: green; float: left; margin-top: -4px; white-space: pre; }" + '.' + Player_Runnerup_Class + " { border: 5px solid red; } " + '.' + Player_Runnerup_Class + " h3::before { content: '(Runner up) '; color: red; float: left; margin-top: -4px; white-space: pre; }" + '</style>', 'sevenwonder_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.gameui.game_name != "sevenwonders") { return; } // Prevent multiple launches if (window.parent.pythia && window.parent.pythia.isStarted) { return; } else { if (Enable_Logging) console.log("PYTHIA: I have come to serve you"); window.parent.pythia = pythia.init(); } } };