Tiberium Alliances Real POI Bonus

Displays actual gain/loss for POIs by taking rank multiplier properly into account

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name           Tiberium Alliances Real POI Bonus
// @version        1.0.1
// @namespace      https://openuserjs.org/users/petui
// @license        GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
// @author         petui
// @description    Displays actual gain/loss for POIs by taking rank multiplier properly into account
// @include        http*://prodgame*.alliances.commandandconquer.com/*/index.aspx*
// ==/UserScript==
'use strict';

(function() {
	var main = function() {
		'use strict';

		function createRealPOIBonus() {
			console.log('RealPOIBonus loaded');

			qx.Class.define('RealPOIBonus', {
				type: 'singleton',
				extend: qx.core.Object,
				statics: {
					PoiTypeToPoiRankingTypeMap: {},
					PoiRankingTypeToSortColumnMap: {}
				},
				defer: function(statics) {
					statics.PoiTypeToPoiRankingTypeMap[ClientLib.Base.EPOIType.TiberiumBonus] = ClientLib.Data.Ranking.ERankingType.BonusTiberium;
					statics.PoiTypeToPoiRankingTypeMap[ClientLib.Base.EPOIType.CrystalBonus] = ClientLib.Data.Ranking.ERankingType.BonusCrystal;
					statics.PoiTypeToPoiRankingTypeMap[ClientLib.Base.EPOIType.PowerBonus] = ClientLib.Data.Ranking.ERankingType.BonusPower;
					statics.PoiTypeToPoiRankingTypeMap[ClientLib.Base.EPOIType.InfanteryBonus] = ClientLib.Data.Ranking.ERankingType.BonusInfantry;
					statics.PoiTypeToPoiRankingTypeMap[ClientLib.Base.EPOIType.VehicleBonus] = ClientLib.Data.Ranking.ERankingType.BonusVehicles;
					statics.PoiTypeToPoiRankingTypeMap[ClientLib.Base.EPOIType.AirBonus] = ClientLib.Data.Ranking.ERankingType.BonusAircraft;
					statics.PoiTypeToPoiRankingTypeMap[ClientLib.Base.EPOIType.DefenseBonus] = ClientLib.Data.Ranking.ERankingType.BonusDefense;

					statics.PoiRankingTypeToSortColumnMap[ClientLib.Data.Ranking.ERankingType.BonusTiberium] = ClientLib.Data.Ranking.ESortColumn.TiberiumScore;
					statics.PoiRankingTypeToSortColumnMap[ClientLib.Data.Ranking.ERankingType.BonusCrystal] = ClientLib.Data.Ranking.ESortColumn.CrystalScore;
					statics.PoiRankingTypeToSortColumnMap[ClientLib.Data.Ranking.ERankingType.BonusPower] = ClientLib.Data.Ranking.ESortColumn.PowerScore;
					statics.PoiRankingTypeToSortColumnMap[ClientLib.Data.Ranking.ERankingType.BonusInfantry] = ClientLib.Data.Ranking.ESortColumn.InfantryScore;
					statics.PoiRankingTypeToSortColumnMap[ClientLib.Data.Ranking.ERankingType.BonusVehicles] = ClientLib.Data.Ranking.ESortColumn.VehicleScore;
					statics.PoiRankingTypeToSortColumnMap[ClientLib.Data.Ranking.ERankingType.BonusAircraft] = ClientLib.Data.Ranking.ESortColumn.AircraftScore;
					statics.PoiRankingTypeToSortColumnMap[ClientLib.Data.Ranking.ERankingType.BonusDefense] = ClientLib.Data.Ranking.ESortColumn.DefenseScore;
				},
				members: {
					rankingBonusDataCache: {},
					container: null,
					titleLabel: null,
					amountLabel: null,
					ownedPoiCount: 0,

					initialize: function() {
						this.initializeHacks();

						this.container = new qx.ui.container.Composite(new qx.ui.layout.HBox(4)).set({
							textColor: 'text-region-tooltip',
							marginRight: 10
						});
						this.container.add(this.titleLabel = new qx.ui.basic.Label());
						this.container.add(this.amountLabel = new qx.ui.basic.Label());

						var poiStatusInfo = webfrontend.gui.region.RegionPointOfInterestStatusInfo.getInstance();
						poiStatusInfo.getChildren()[0].addAt(this.container, 4);
						poiStatusInfo.addListener('appear', this.onStatusInfoAppear, this);

						phe.cnc.Util.attachNetEvent(ClientLib.Data.MainData.GetInstance().get_Alliance(), 'Change', ClientLib.Data.AllianceChange, this, this.onAllianceChange);
						this.onAllianceChange();
					},

					initializeHacks: function() {
						if (typeof webfrontend.gui.region.RegionPointOfInterestStatusInfo.prototype.getObject !== 'function') {
							var source = webfrontend.gui.region.RegionPointOfInterestStatusInfo.prototype.setObject.toString();
							var objectMemberName = source.match(/^function \(([A-Za-z]+)\)\{this\.([A-Za-z_]+)=\1;/)[2];

							/**
							 * @returns {ClientLib.Vis.Region.RegionPointOfInterest}
							 */
							webfrontend.gui.region.RegionPointOfInterestStatusInfo.prototype.getObject = function() {
								return this[objectMemberName];
							};
						}
					},

					onAllianceChange: function() {
						var alliance = ClientLib.Data.MainData.GetInstance().get_Alliance();
						var poiCount = alliance.get_Exists() ? alliance.get_OwnedPOIs().length : 0;

						if (poiCount !== this.ownedPoiCount) {
							this.ownedPoiCount = poiCount;
							this.rankingBonusDataCache = {};
						}
					},

					/**
					 * @param {qx.event.type.Event} event
					 */
					onStatusInfoAppear: function(event) {
						var visObject = event.getTarget().getObject();
						var allianceId = ClientLib.Data.MainData.GetInstance().get_Alliance().get_Id();

						if (allianceId > 0 && visObject.get_Type() !== ClientLib.Data.WorldSector.WorldObjectPointOfInterest.EPOIType.TunnelExit) {
							var selectedPoiScore = ClientLib.Base.PointOfInterestTypes.GetScoreByLevel(visObject.get_Level());
							var poiType = ClientLib.Base.PointOfInterestTypes.GetPOITypeFromWorldPOIType(visObject.get_Type());
							var poiRankScore = ClientLib.Data.MainData.GetInstance().get_Alliance().get_POIRankScore()[poiType - ClientLib.Base.EPOIType.RankedTypeBegin];
							var allianceRank = poiRankScore.r;
							var allianceScore = poiRankScore.s;
							var nextAllianceScore = poiRankScore.ns;
							var previousAllianceScore = poiRankScore.ps;
							var currentTotalBonus = ClientLib.Base.PointOfInterestTypes.GetTotalBonusByType(poiType, allianceRank, allianceScore);
							var gainOrLoss = null;

							if (visObject.get_OwnerAllianceId() === allianceId) {
								this.titleLabel.setValue('Real loss:');

								if (previousAllianceScore <= 0) {
									// No rank multiplier; no loss by rank
									gainOrLoss = currentTotalBonus - ClientLib.Base.PointOfInterestTypes.GetTotalBonusByType(poiType, allianceRank, allianceScore - selectedPoiScore);
								}
								else if (allianceScore - selectedPoiScore < previousAllianceScore) {
									// Falling behind previous alliance; need to use rankings
								}
								else {
									// No loss by rank; if we end up with same score as previous alliance, our rank stays the same and they get same rank
									gainOrLoss = currentTotalBonus - ClientLib.Base.PointOfInterestTypes.GetTotalBonusByType(poiType, allianceRank, allianceScore - selectedPoiScore);
								}
							}
							else {
								this.titleLabel.setValue('Real gain:');

								if (!allianceScore) {
									// Zero bonus; need to use rankings
								}
								else if (nextAllianceScore <= 0 || allianceRank <= 1) {
									// Already rank 1; no gain by rank
									gainOrLoss = ClientLib.Base.PointOfInterestTypes.GetTotalBonusByType(poiType, allianceRank, allianceScore + selectedPoiScore) - currentTotalBonus;
								}
								else if (visObject.get_OwnerAllianceId() !== webfrontend.gui.widgets.AllianceLabel.ESpecialNoAllianceName) {
									// Current owner of POI will lose score while we gain; need to use rankings
								}
								else if (allianceScore + selectedPoiScore > nextAllianceScore) {
									// Overtaking next alliance; need to use rankings
								}
								else if (allianceScore + selectedPoiScore < nextAllianceScore) {
									// No gain by rank
									gainOrLoss = ClientLib.Base.PointOfInterestTypes.GetTotalBonusByType(poiType, allianceRank, allianceScore + selectedPoiScore) - currentTotalBonus;
								}
								else {
									// Same score as next alliance; same rank and same bonus as them
									gainOrLoss = ClientLib.Base.PointOfInterestTypes.GetTotalBonusByType(poiType, allianceRank - 1, allianceScore + selectedPoiScore) - currentTotalBonus;
								}
							}

							if (gainOrLoss === null) {
								this.amountLabel.setValue('Loading...');
								this.fetchAndCalculateBonusWithRankingData(poiType, allianceRank, allianceScore, selectedPoiScore, allianceId, visObject.get_OwnerAllianceId());
							}
							else {
								this.amountLabel.setValue(this.formatGainOrLoss(gainOrLoss, poiType));
							}

							this.container.setVisibility('visible');
						}
						else {
							this.container.setVisibility('excluded');
						}
					},

					/**
					 * @param {ClientLib.Base.EPOIType} poiType
					 * @param {Number} currentRank
					 * @param {Number} currentScore
					 * @param {Number} poiScore
					 * @param {Number} allianceId
					 * @param {Number} poiOwnerId
					 */
					fetchAndCalculateBonusWithRankingData: function(poiType, currentRank, currentScore, poiScore, allianceId, poiOwnerId) {
						var context = {
							poiType: poiType,
							currentRank: currentRank,
							currentScore: currentScore,
							poiScore: poiScore,
							allianceId: allianceId,
							poiOwnerId: poiOwnerId
						};

						if (poiType in this.rankingBonusDataCache && this.rankingBonusDataCache[poiType].expire >= Date.now()) {
							this.calculateBonus(context, this.rankingBonusDataCache[poiType].results);
						}
						else {
							var lastMultiplierRank = Object.keys(ClientLib.Res.ResMain.GetInstance().GetGamedata().poibmbr).length;
							var rankingPoiType = RealPOIBonus.PoiTypeToPoiRankingTypeMap[poiType];
							var sortColumn = RealPOIBonus.PoiRankingTypeToSortColumnMap[rankingPoiType];

							ClientLib.Net.CommunicationManager.GetInstance().SendSimpleCommand('RankingGetData', {
								firstIndex: 0,
								lastIndex: lastMultiplierRank,
								view: ClientLib.Data.Ranking.EViewType.Alliance,
								rankingType: rankingPoiType,
								sortColumn: sortColumn,
								ascending: true
							}, phe.cnc.Util.createEventDelegate(ClientLib.Net.CommandResult, this, this.onRankingGetData), context);
						}
					},

					/**
					 * @param {Object} context
					 * @param {Object} results
					 */
					onRankingGetData: function(context, results) {
						if (results === null) {
							return;
						}

						var allianceBonuses = results.a;

						// Remove own alliance from list and add missing scores
						for (var i = 0; i < allianceBonuses.length; i++) {
							if (allianceBonuses[i].a === context.allianceId) {
								allianceBonuses.splice(i--, 1);
							}
							else if (allianceBonuses[i].pois === undefined) {
								allianceBonuses[i].pois = 0;
							}
						}

						this.rankingBonusDataCache[context.poiType] = {
							expire: Date.now() + 600000,
							results: allianceBonuses
						};

						this.calculateBonus(context, allianceBonuses);
					},

					/**
					 * @param {Object} context
					 * @param {Array} allianceBonuses
					 */
					calculateBonus: function(context, allianceBonuses) {
						var isGain = context.poiOwnerId !== context.allianceId;
						var i;

						if (isGain && context.poiOwnerId !== webfrontend.gui.widgets.AllianceLabel.ESpecialNoAllianceName) {
							// Subtract POI score from current owner
							for (i = 0; i < allianceBonuses.length; i++) {
								if (allianceBonuses[i].a === context.poiOwnerId) {
									// Array can be safely modified after cloning
									allianceBonuses = allianceBonuses.map(this.shallowClone);
									allianceBonuses[i].pois -= context.poiScore;

									allianceBonuses.sort(function(a, b) {
										return b.pois - a.pois;
									});
									break;
								}
							}
						}

						var newAllianceScore = context.currentScore + (isGain ? context.poiScore : -context.poiScore);

						for (i = 0; i < allianceBonuses.length; i++) {
							if (allianceBonuses[i].pois <= newAllianceScore) {
								break;
							}
						}

						var currentTotalBonus = ClientLib.Base.PointOfInterestTypes.GetTotalBonusByType(context.poiType, context.currentRank, context.currentScore);
						var newTotalBonus = ClientLib.Base.PointOfInterestTypes.GetTotalBonusByType(context.poiType, i + 1, newAllianceScore);
						var gainOrLoss = isGain
							? newTotalBonus - currentTotalBonus
							: currentTotalBonus - newTotalBonus;

						this.amountLabel.setValue(this.formatGainOrLoss(gainOrLoss, context.poiType));
					},

					/**
					 * @param {Number} gainOrLoss
					 * @param {ClientLib.Base.EPOIType} poiType
					 * @returns {String}
					 */
					formatGainOrLoss: function(gainOrLoss, poiType) {
						switch (poiType) {
							case ClientLib.Base.EPOIType.TiberiumBonus:
							case ClientLib.Base.EPOIType.CrystalBonus:
							case ClientLib.Base.EPOIType.PowerBonus:
								return phe.cnc.gui.util.Numbers.formatNumbers(gainOrLoss) + '/h';
							case ClientLib.Base.EPOIType.InfanteryBonus:
							case ClientLib.Base.EPOIType.VehicleBonus:
							case ClientLib.Base.EPOIType.AirBonus:
							case ClientLib.Base.EPOIType.DefenseBonus:
								return phe.cnc.gui.util.Numbers.formatNumbers(gainOrLoss) + '%';
						};
					},

					/**
					 * @param {Object} object
					 * @returns {Object}
					 */
					shallowClone: function(object) {
						var clone = new object.constructor;

						for (var key in object) {
							if (object.hasOwnProperty(key)) {
								clone[key] = object[key];
							}
						}

						return clone;
					}
				}
			});
		}

		function waitForGame() {
			try {
				if (typeof qx !== 'undefined' && qx.core.Init.getApplication() && qx.core.Init.getApplication().initDone) {
					createRealPOIBonus();
					RealPOIBonus.getInstance().initialize();
				}
				else {
					setTimeout(waitForGame, 1000);
				}
			}
			catch (e) {
				console.log('RealPOIBonus: ', e.toString());
			}
		}

		setTimeout(waitForGame, 1000);
	};

	var script = document.createElement('script');
	script.innerHTML = '(' + main.toString() + ')();';
	script.type = 'text/javascript';
	document.getElementsByTagName('head')[0].appendChild(script);
})();