NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Tiberium Alliances Tweaks // @version 1.2.0 // @namespace https://openuserjs.org/users/petui // @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html // @author petui // @description A collection of more or less useful features that attempt to improve the gaming experience. // @include http*://prodgame*.alliances.commandandconquer.com/*/index.aspx* // @updateURL https://openuserjs.org/meta/petui/Tiberium_Alliances_Tweaks.meta.js // ==/UserScript== 'use strict'; (function() { var main = function() { 'use strict'; function createTweaks() { console.log('Tweaks loaded'); qx.Class.define('Tweaks', { type: 'singleton', extend: qx.core.Object, statics: { Category: { Useful: 1, Bugfix: 2, Script: 3, Uncategorized: 4, Self: 5 }, CategoryNames: {} }, defer: function(statics) { statics.CategoryNames[Tweaks.Category.Useful] = 'Useful stuff'; statics.CategoryNames[Tweaks.Category.Bugfix] = 'Game bugfixes'; statics.CategoryNames[Tweaks.Category.Script] = '3rd party script tuning'; statics.CategoryNames[Tweaks.Category.Uncategorized] = 'Uncategorized'; statics.CategoryNames[Tweaks.Category.Self] = 'Settings'; }, construct: function() { this.features = {}; this.configs = this.loadSettings(); this.settingsWindow = new Tweaks.SettingsWindow(this, Tweaks.CategoryNames); this.settingsWindow.addListener('change', this.onSettingsChange, this); }, events: { initialize: 'qx.event.type.Event', addFeature: 'qx.event.type.Data', saveSettings: 'qx.event.type.Event' }, members: { settingsWindow: null, features: null, configs: null, initialized: false, initialize: function() { this.initializeEntryPoint(); this.initialized = true; this.fireEvent('initialize'); if (!this.hasSavedSettings()) { webfrontend.gui.MessageBox.messageBox({ modal: false, textRich: true, title: 'Would you like to configure Tweaks?', text: 'Looks like this is your first time running Tweaks in this server. Open settings now?<br/><br/>' + 'You can always access it later in the navigation bar under <i>Scripts</i>.', okText: 'Yes', cancelText: 'No', executeOk: this.openSettingsWindow, callbackContext: this }); // Save empty settings so user won't be asked again this.saveSettings({}); } }, initializeEntryPoint: function() { var scriptsButton = qx.core.Init.getApplication().getMenuBar().getScriptsButton(); scriptsButton.Add('Tweaks', 'FactionUI/icons/icon_mode_repair.png'); var children = scriptsButton.getMenu().getChildren(); var lastChild = children[children.length - 1]; lastChild.addListener('execute', this.openSettingsWindow, this); lastChild.getChildControl('icon').set({ scale: true, width: 18, height: 18 }); }, /** * @param {qx.event.type.Data} event */ onSettingsChange: function(event) { var encounteredError = false; var settings = event.getData(); for (var classname in settings) { var isEnabled = settings[classname].enabled; var details = this.features[classname]; var wasEnabled = this.hasConfig(details.instance) ? this.getConfig(details.instance).enabled : false; this.configs[details.options.configKey] = settings[classname]; try { if (isEnabled) { details.instance.activate(wasEnabled); } else { details.instance.deactivate(wasEnabled); } } catch (e) { encounteredError = true; this.settingsWindow.addError(details.container, (isEnabled ? 'Activation' : 'Deactivation') + ' failed'); qx.event.GlobalError.handleError(e); } } this.saveSettings(this.configs); try { this.fireEvent('saveSettings'); } catch (e) { qx.event.GlobalError.handleError(e); } if (encounteredError) { this.settingsWindow.open(); } }, /** * @returns {Boolean} */ hasSavedSettings: function() { return localStorage.getItem('Tweaks') !== null; }, /** * @returns {Object} */ loadSettings: function() { return JSON.parse(localStorage.getItem('Tweaks')) || {}; }, /** * @param {Object} settings */ saveSettings: function(settings) { localStorage.setItem('Tweaks', JSON.stringify(settings)); }, /** * @param {Tweaks.Feature.IFeature} featureConstructor * @param {Object} options */ registerFeature: function(featureConstructor, options) { qx.Interface.assert(featureConstructor, Tweaks.Feature.IFeature, true); if (featureConstructor.classname in this.features) { throw new Error('Feature "' + featureConstructor.classname + '" is already registered'); } var instance = null; var normalizedOptions = { name: options.name || featureConstructor.classname, description: options.description || null, category: options.category || Tweaks.Category.Uncategorized, configKey: options.configKey || featureConstructor.classname, disabled: options.disabled || false }; var featureKey = featureConstructor.classname; this.features[featureKey] = { construct: featureConstructor, container: null, instance: null, options: normalizedOptions }; if (!normalizedOptions.disabled) { try { instance = new featureConstructor; this.features[featureKey].instance = instance; } catch (e) { qx.event.GlobalError.handleError(e); } } var config = this.getConfig(featureConstructor); var container = this.settingsWindow.addFeature(instance, normalizedOptions, config); this.features[featureKey].container = container; if (normalizedOptions.disabled) { this.settingsWindow.addMessage(container, 'Disabled', normalizedOptions.disabled); } else if (instance === null) { this.settingsWindow.addError(container, 'Failed to instantiate'); } else if (config.enabled) { try { instance.activate(false); } catch (e) { this.settingsWindow.addError(container, 'Activation failed'); qx.event.GlobalError.handleError(e); } } try { this.fireDataEvent('addFeature', this.shallowClone(this.features[featureKey])); } catch (e) { qx.event.GlobalError.handleError(e); } }, /** * @returns {Object} */ getAllFeatures: function() { return this.shallowClone(this.features); }, /** * @param {Tweaks.Feature.IFeature} feature * @returns {Boolean} */ hasConfig: function(feature) { return feature.classname in this.features && this.features[feature.classname].options.configKey in this.configs; }, /** * @param {Tweaks.Feature.IFeature} feature * @returns {Object} */ getConfig: function(feature) { if (!(feature.classname in this.features)) { throw new Error('Feature "' + feature.classname + '" is not registered'); } var options = this.features[feature.classname].options; return options.configKey in this.configs ? this.shallowClone(this.configs[options.configKey]) : {}; }, openSettingsWindow: function() { this.settingsWindow.open(); }, /** * @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; } } }); qx.Class.define('Tweaks.SettingsWindow', { extend: qx.ui.window.Window, statics: { IndentStep: 20 }, construct: function(core, categories) { qx.ui.window.Window.call(this); this.core = core; this.set({ caption: 'Tweaks', icon: 'FactionUI/icons/icon_mode_repair.png', layout: new qx.ui.layout.VBox(18), contentPaddingTop: 0, contentPaddingBottom: 5, contentPaddingRight: 6, contentPaddingLeft: 6, showMaximize: false, showMinimize: false, allowMaximize: false, allowMinimize: false, textColor: 'text-region-tooltip', resizable: [true, false], width: 500 }); this.getChildControl('icon').set({ scale: true, width: 20, height: 20, alignY: 'middle' }); var mainContainer = qx.core.Init.getApplication().getMainContainer(); mainContainer.addListener('resize', function(event) { this.setMaxHeight(event.getData().height); }, this); this.setMaxHeight(mainContainer.getBounds().height); var mainOverlayBounds = qx.core.Init.getApplication().getMainOverlay().getBounds(); this.moveTo( mainOverlayBounds.left + mainOverlayBounds.width - this.getWidth() - 120, mainOverlayBounds.top + 100 ); var scrollContainer = new qx.ui.container.Composite(new qx.ui.layout.VBox()); var scroller = new qx.ui.container.Scroll(scrollContainer); scrollContainer.addListener('resize', function(event) { this.setHeight(event.getData().height); }, scroller); this.add(scroller, { flex: 1 }); this.categoryContainers = {}; this.features = []; for (var categoryId in categories) { var container = this.categoryContainers[categoryId] = new qx.ui.container.Composite(new qx.ui.layout.VBox(5)).set({ marginLeft: 2, visibility: 'excluded' }); container.add(new qx.ui.basic.Label(categories[categoryId]).set({ font: 'font_size_13', textColor: 'text-region-value' })); scrollContainer.add(container); } var cancelButton = new qx.ui.form.Button('Cancel').set({ paddingLeft: 10, paddingRight: 10 }); cancelButton.addListener('execute', this.onCancelClick, this); var saveButton = new qx.ui.form.Button('Save').set({ paddingLeft: 10, paddingRight: 10 }); saveButton.addListener('execute', this.onSaveClick, this); var controlsContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(4)); controlsContainer.add(cancelButton, { flex: 1 }); controlsContainer.add(saveButton, { flex: 1 }); this.add(controlsContainer); }, events: { change: 'qx.event.type.Data' }, members: { core: null, configs: null, categoryContainers: null, features: null, onCancelClick: function() { var encounteredError = false; for (var i = 0; i < this.features.length; i++) { var details = this.features[i]; var config = this.core.getConfig(details.instance); details.checkbox.setValue(config.enabled || false); try { details.instance.onReset(config); } catch (e) { encounteredError = true; this.addError(details.container, 'Failed to reset settings'); qx.event.GlobalError.handleError(e); } } if (!encounteredError) { this.close(); } }, onSaveClick: function() { var encounteredError = false; var configs = {}; for (var i = 0; i < this.features.length; i++) { var details = this.features[i]; var isEnabled = details.checkbox.getValue(); var config = { enabled: isEnabled }; try { details.instance.onSaveConfig(config); } catch (e) { encounteredError = true; this.addError(details.container, 'Failed to save settings'); qx.event.GlobalError.handleError(e); } configs[details.instance.classname] = config; } if (!encounteredError) { this.close(); } this.fireDataEvent('change', configs); }, /** * @param {Tweaks.Feature.IFeature} feature * @param {Object} options * @param {Object} config * @returns {qx.ui.container.Composite} */ addFeature: function(feature, options, config) { var checkbox = new qx.ui.form.CheckBox(options.name).set({ value: config.enabled || false }); var label = options.description ? new qx.ui.basic.Label(options.description).set({ rich: true }) : null; var renderFailed = false; var container = null; if (feature !== null) { try { var temp = feature.onRender(checkbox, label, config); if (temp) { qx.core.Assert.assertInstance(temp, qx.ui.container.Composite); container = temp; } } catch (e) { checkbox.setEnabled(false); renderFailed = true; qx.event.GlobalError.handleError(e); } } else { checkbox.set({ enabled: false, value: false }); } if (!container) { container = new qx.ui.container.Composite(new qx.ui.layout.VBox()); container.add(checkbox); if (label !== null) { container.add(label); } } container.setMarginLeft(10); var categoryContainer = this.categoryContainers[options.category]; categoryContainer.add(container); if (!categoryContainer.isVisible()) { categoryContainer.show(); } if (renderFailed) { this.addError(container, 'Failed to render settings'); } else if (feature !== null) { this.features.push({ checkbox: checkbox, container: container, instance: feature }); } return container; }, /** * @param {qx.ui.container.Composite} container * @param {String} message */ addError: function(container, message) { this.addMessage(container, 'Error', message); }, /** * @param {qx.ui.container.Composite} container * @param {String} title * @param {String} message */ addMessage: function(container, title, message) { container.add(new qx.ui.basic.Label().set({ rich: true, value: '<span style="color: #f00;">' + title + ': ' + message + '</span>' })); } } }); qx.Class.define('Tweaks.NotificationButton', { extend: qx.ui.form.Button, construct: function(label, icon, command) { qx.ui.form.Button.call(this, label, icon, command); this.set({ margin: 1, padding: [0, 0, 2] }); this._setLayout(new qx.ui.layout.Canvas()); this.getContentElement().setStyle('overflow', 'visible'); }, members: { /** @inheritDoc */ _createChildControlImpl: function(id) { var child; switch (id) { case 'label': child = qx.ui.form.Button.prototype._createChildControlImpl.apply(this, arguments).set({ backgroundColor: 'white', font: 'font_size_13_bold', padding: [1, 6], textColor: 'black' }); child.setLayoutProperties({ right: -2, top: -2 }); var containerElement = PerforceChangelist >= 430398 ? child.getContentElement() : child.getContainerElement(); containerElement.setStyle('border-radius', '8px'); break; case 'icon': child = qx.ui.form.Button.prototype._createChildControlImpl.apply(this, arguments).set({ margin: [4, 6, 6] }); break; }; return child || qx.ui.form.Button.prototype._createChildControlImpl.apply(this, arguments); }, _applyCenter: function() {}, _applyGap: function() {}, _applyIconPosition: function() {} } }); qx.Interface.define('Tweaks.Feature.IFeature', { members: { /** * Called when the feature is about to be rendered in settings window. * Return an instance of a qx.ui.container.Composite to change the appearance. * * @param {qx.ui.form.CheckBox} checkbox * @param {qx.ui.basic.Label} label * @param {Object} config * @returns {qx.ui.container.Composite} */ onRender: function(checkbox, label, config) {}, /** * Called when settings are being reseted. * * @param {Object} config */ onReset: function(config) {}, /** * Called when settings are being saved. * * @param {Object} config */ onSaveConfig: function(config) {}, /** * Called when the feature is to be (re)activated. * * @param {Boolean} wasActive */ activate: function(wasActive) {}, /** * Called when the feature is to be (re)deactivated. * * @param {Boolean} wasActive */ deactivate: function(wasActive) {} } }); qx.Class.define('Tweaks.Feature.AlternativeErrorHandler', { extend: qx.core.Object, implement: [Tweaks.Feature.IFeature], defer: function(statics, members) { Tweaks.getInstance().registerFeature(members.constructor, { category: Tweaks.Category.Useful, name: 'Alternative error handler', description: 'A non-blocking error handler to replace the error reporting dialog.', configKey: 'AlternativeErrorHandler' }); }, construct: function() { if (typeof qx.event.GlobalError.getErrorHandler !== 'function') { var source = qx.event.GlobalError.handleError.toString(); var matches = source.match(/this\.([A-Za-z_]+)\.call\(this\.([A-Za-z_]+),[A-Za-z]+\);/); var callbackMemberName = matches[1]; var contextMemberName = matches[2]; qx.event.GlobalError.getErrorHandler = eval('(function(){return {callback:this.' + callbackMemberName + ',context:this.' + contextMemberName + '};})'); } }, members: { openWindowOnErrorCheckbox: null, desktopButton: null, window: null, errorCount: 0, originalErrorHandler: null, shouldOpenWindowOnError: null, /** @inheritDoc */ onRender: function(checkbox, label, config) { var container = new qx.ui.container.Composite(new qx.ui.layout.VBox()); container.add(checkbox); container.add(this.openWindowOnErrorCheckbox = new qx.ui.form.CheckBox().set({ label: 'Open error log automatically when an error is encountered', marginLeft: Tweaks.SettingsWindow.IndentStep, value: config.openWindowOnError || false })); container.add(label); checkbox.bind('value', this.openWindowOnErrorCheckbox, 'enabled'); return container; }, /** @inheritDoc */ onReset: function(config) { this.openWindowOnErrorCheckbox.setValue(config.openWindowOnError || false); }, /** @inheritDoc */ onSaveConfig: function(config) { config.openWindowOnError = this.openWindowOnErrorCheckbox.getValue(); }, /** @inheritDoc */ activate: function(wasActive) { this.shouldOpenWindowOnError = this.openWindowOnErrorCheckbox.getValue(); if (wasActive) { return; } this.originalErrorHandler = qx.event.GlobalError.getErrorHandler(); qx.event.GlobalError.setErrorHandler(this.handleError, this); }, deactivate: function(wasActive) { if (!wasActive) { return; } if (this.errorCount > 0 && this.originalErrorHandler.context === qx.core.Init.getApplication()) { qx.event.GlobalError.setErrorHandler(null, null); } else { qx.event.GlobalError.setErrorHandler(this.originalErrorHandler.callback, this.originalErrorHandler.context); this.originalErrorHandler = null; } }, onClickDesktopButton: function() { this.getWindow().open(); }, /** * @param {Error} error */ handleError: function(error) { console.error(error.stack ? error.stack : error); this.errorCount++; var desktopButton = this.getDesktopButton(); desktopButton.setLabel(this.errorCount.toString()); desktopButton.show(); var window = this.getWindow(); window.push(error); if (this.shouldOpenWindowOnError) { window.open(); } }, /** * @returns {Tweaks.NotificationButton} */ getDesktopButton: function() { if (this.desktopButton === null) { this.desktopButton = new Tweaks.NotificationButton().set({ appearance: 'button-standard-nod', icon: 'webfrontend/ui/icons/icn_show_combat_active.png', toolTipText: 'Click to open error log' }); this.desktopButton.getChildControl('icon').set({ scale: true, width: 36, height: 32 }); this.desktopButton.addListener('execute', this.onClickDesktopButton, this); qx.core.Init.getApplication().getDesktop().add(this.desktopButton, { right: 125, top: 40 }); } return this.desktopButton; }, /** * @returns {Tweaks.Feature.AlternativeErrorHandler.ErrorWindow} */ getWindow: function() { if (this.window === null) { this.window = new Tweaks.Feature.AlternativeErrorHandler.ErrorWindow(); var baseNavBarX = qx.core.Init.getApplication().getBaseNavigationBar().getLayoutParent().getBounds().left; this.window.moveTo(baseNavBarX - this.window.getWidth() - 60, 40); } return this.window; } } }); qx.Class.define('Tweaks.Feature.AlternativeErrorHandler.ErrorWindow', { extend: qx.ui.window.Window, construct: function() { qx.ui.window.Window.call(this); this.set({ caption: 'Errors', icon: 'webfrontend/ui/common/icon_moral_alert_red.png', layout: new qx.ui.layout.VBox(4), width: 450, height: 200, contentPaddingTop: 0, contentPaddingBottom: 6, contentPaddingRight: 6, contentPaddingLeft: 6, showMaximize: false, showMinimize: false, allowMaximize: false, allowMinimize: false, resizable: true, visibility: 'excluded', textColor: '#bfbfbf' }); this.getChildControl('icon').set({ scale: true, width: 18, height: 17, alignY: 'middle', marginLeft: 8 }); this.add(this.logContainer = new qx.ui.container.Composite(new qx.ui.layout.VBox())); this.add(new qx.ui.core.Spacer(), { flex: 1 }); var reportContainer = new qx.ui.container.Composite(new qx.ui.layout.VBox()); reportContainer.add(this.reportButton = new qx.ui.form.Button('Report first error').set({ alignX: 'center', allowGrowX: false, enabled: false, toolTipText: 'Click to open the error reporting dialog' })); this.reportButton.addListener('execute', this.onClickReportButton, this); this.add(reportContainer); }, members: { logContainer: null, reportButton: null, /** * @param {Error} error */ push: function(error) { if (!this.reportButton.isEnabled() && !this.logContainer.getChildren().length) { this.reportButton.setUserData('error', error); this.reportButton.setEnabled(true); } this.logContainer.add(new qx.ui.basic.Label( phe.cnc.Util.getDateTimeString(new Date) + ' ' + error.toString() )); }, onClickReportButton: function() { this.reportButton.setEnabled(false); var error = this.reportButton.getUserData('error'); var app = qx.core.Init.getApplication(); var errorHandler = qx.event.GlobalError.getErrorHandler(); app.handleError(error); if (errorHandler.context !== app) { // Restore error handler that webfrontend.Application.prototype.handleError removed qx.event.GlobalError.setErrorHandler(errorHandler.callback, errorHandler.context); } } } }); qx.Class.define('Tweaks.Feature.ShrinkableWindows', { extend: qx.core.Object, implement: [Tweaks.Feature.IFeature], defer: function(statics, members) { Tweaks.getInstance().registerFeature(members.constructor, { category: Tweaks.Category.Useful, name: 'Shrinkable windows', description: 'Click the minimize button to shrink windows. Note that overlays look similar to windows, but they are not shrinkable.', configKey: 'ShrinkableWindows' }); }, members: { onRender: function(checkbox, label, config) {}, onReset: function(config) {}, onSaveConfig: function(config) {}, /** @inheritDoc */ activate: function(wasActive) { if (wasActive) { return; } var context = this; var root = qx.core.Init.getApplication().getRoot(); var windows = root.getWindows(); root._addWindow = function(window) { context.enableWindowShrink(window); return qx.ui.root.Application.prototype._addWindow.apply(this, arguments); }; root._removeWindow = function(window) { context.disableWindowShrink(window); return qx.ui.root.Application.prototype._removeWindow.apply(this, arguments); }; for (var i = 0; i < windows.length; i++) { this.enableWindowShrink(windows[i]); } }, /** @inheritDoc */ deactivate: function(wasActive) { if (!wasActive) { return; } var root = qx.core.Init.getApplication().getRoot(); var windows = root.getWindows(); root._addWindow = qx.ui.root.Application.prototype._addWindow; root._removeWindow = qx.ui.root.Application.prototype._removeWindow; for (var i = 0; i < windows.length; i++) { this.disableWindowShrink(windows[i]); } }, /** * @param {qx.ui.window.Window} window */ enableWindowShrink: function(window) { if (window.getUserData('Tweaks.Shrinkable')) { return; } window.setUserData('Tweaks.Shrinkable', true); window.setUserData('Tweaks.AllowMinimize', window.getAllowMinimize()); window.setUserData('Tweaks.ShowMinimize', window.getShowMinimize()); window.setAllowMinimize = function(value) { this.setUserData('Tweaks.AllowMinimize', value); }; window.setShowMinimize = function(value) { this.setUserData('Tweaks.ShowMinimize', value); }; window.constructor.prototype.setAllowMinimize.call(window, true); window.constructor.prototype.setShowMinimize.call(window, true); window.addListener('beforeMinimize', this.onBeforeWindowMinimize, this); window.addListener('disappear', this.onWindowDisappear, this); }, /** * @param {qx.ui.window.Window} window */ disableWindowShrink: function(window) { if (!window.getUserData('Tweaks.Shrinkable')) { return; } window.removeListener('beforeMinimize', this.onBeforeWindowMinimize, this); window.removeListener('disappear', this.onWindowDisappear, this); window.setAllowMinimize = window.constructor.prototype.setAllowMinimize; window.setShowMinimize = window.constructor.prototype.setShowMinimize; window.setAllowMinimize(window.getUserData('Tweaks.AllowMinimize')); window.setShowMinimize(window.getUserData('Tweaks.ShowMinimize')); window.setUserData('Tweaks.AllowMinimize', undefined); window.setUserData('Tweaks.ShowMinimize', undefined); window.setUserData('Tweaks.Shrinkable', undefined); this.restoreWindowContent(window); }, /** * @param {qx.event.type.Event} event */ onBeforeWindowMinimize: function(event) { event.preventDefault(); var window = event.getTarget(); var pane = window.getChildrenContainer(); if (window.getUserData('Tweaks.Dimensions') === null) { window.setUserData('Tweaks.Dimensions', { height: window.getHeight(), minHeight: window.getMinHeight(), width: window.getWidth() }); pane.exclude(); window.set({ height: null, minHeight: null, width: window.getBounds().width }); } else { this.restoreWindowContent(window); } }, /** * @param {qx.event.type.Event} event */ onWindowDisappear: function(event) { var window = event.getTarget(); this.restoreWindowContent(window); }, /** * @param {qx.ui.window.Window} window */ restoreWindowContent: function(window) { var dimensions = window.getUserData('Tweaks.Dimensions'); if (dimensions !== null) { window.set({ height: dimensions.height, minHeight: dimensions.minHeight, width: dimensions.width }); window.getChildrenContainer().show(); window.setUserData('Tweaks.Dimensions', undefined); } } } }); qx.Class.define('Tweaks.Feature.MovableMessageComposingWindow', { extend: qx.core.Object, implement: [Tweaks.Feature.IFeature], defer: function(statics, members) { Tweaks.getInstance().registerFeature(members.constructor, { category: Tweaks.Category.Useful, name: 'Movable message composing window', description: 'Replaces the message composing overlay with its window equivalent. Also enables shrinking if that option is activated.', configKey: 'MovableMessageComposingWindow' }); }, construct: function() { var source = webfrontend.gui.mail.MailOverlay.prototype.onNewMessage.toString(); this.mailOverlayMessageMemberName = source.match(/this\.([A-Za-z_]+)\.open/)[1]; }, members: { mailOverlayMessageMemberName: null, onRender: function(checkbox, label, config) {}, onReset: function(config) {}, onSaveConfig: function(config) {}, /** @inheritDoc */ activate: function(wasActive) { if (wasActive) { return; } var mailMessageWindow = new webfrontend.gui.mail.MailMessageWindow(); mailMessageWindow.open = function(returnWidget, writeType) { webfrontend.gui.mail.MailMessageWindow.prototype.open.call(this, null, writeType); }; // Use methods from MailMessageOverlay to fix reply and forward mailMessageWindow.setSubject = webfrontend.gui.mail.MailMessageOverlay.prototype.setSubject; mailMessageWindow.setHistoryEntries = webfrontend.gui.mail.MailMessageOverlay.prototype.setHistoryEntries; webfrontend.gui.mail.MailOverlay.getInstance()[this.mailOverlayMessageMemberName] = mailMessageWindow; }, /** @inheritDoc */ deactivate: function(wasActive) { if (!wasActive) { return; } webfrontend.gui.mail.MailOverlay.getInstance()[this.mailOverlayMessageMemberName] = new webfrontend.gui.mail.MailMessageOverlay(); } } }); qx.Class.define('Tweaks.Feature.ExtendedChatHistory', { extend: qx.core.Object, implement: [Tweaks.Feature.IFeature], defer: function(statics, members) { Tweaks.getInstance().registerFeature(members.constructor, { category: Tweaks.Category.Useful, name: 'Extended chat history', description: 'Increases chat history length.', configKey: 'ExtendedChatHistory' }); }, construct: function() { var source = webfrontend.gui.chat.ChatWidget.prototype.setTab.toString(); this.tabViewMemberName = source.match(/this\.([A-Za-z_]+)\.setSelection\(/)[1]; // MaelstromTools overwrites webfrontend.gui.chat.ChatWidget.recvbufsize, so use hardcoded value instead. this.defaultLength = /*webfrontend.gui.chat.ChatWidget.recvbufsize*/64; }, members: { tabViewMemberName: null, defaultLength: null, lengthSpinner: null, /** @inheritDoc */ onRender: function(checkbox, label, config) { var rowContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(25)); rowContainer.add(checkbox); rowContainer.add(this.lengthSpinner = new qx.ui.form.Spinner().set({ minimum: this.defaultLength, maximum: 512, value: config.length || this.defaultLength })); checkbox.bind('value', this.lengthSpinner, 'enabled'); var container = new qx.ui.container.Composite(new qx.ui.layout.VBox()); container.add(rowContainer); container.add(label); return container; }, /** @inheritDoc */ onReset: function(config) { this.lengthSpinner.setValue(config.length || this.defaultLength); }, /** @inheritDoc */ onSaveConfig: function(config) { config.length = this.lengthSpinner.getValue(); }, /** @inheritDoc */ activate: function(wasActive) { var length = this.lengthSpinner.getValue(); this.setChatHistoryLength(length); }, /** @inheritDoc */ deactivate: function(wasActive) { if (wasActive) { this.setChatHistoryLength(this.defaultLength); } }, /** * @param {Number} length */ setChatHistoryLength: function(length) { var tabPages = qx.core.Init.getApplication().getChat().getChatWidget()[this.tabViewMemberName].getChildren(); for (var i = 0; i < tabPages.length; i++) { tabPages[i].messages.resize(length); } } } }); qx.Class.define('Tweaks.Feature.AmbientSoundVolumeFix', { extend: qx.core.Object, implement: [Tweaks.Feature.IFeature], defer: function(statics, members) { Tweaks.getInstance().registerFeature(members.constructor, { category: Tweaks.Category.Bugfix, name: 'Fix ambient audio volume', description: 'Fixes ambient audio volume always resetting to 20% on login.', configKey: 'AmbientSoundVolumeFix' }); }, members: { previousPlayAreaSoundVolume: null, onRender: function(checkbox, label, config) {}, onReset: function(config) {}, onSaveConfig: function(config) {}, /** @inheritDoc */ activate: function(wasActive) { if (wasActive) { return; } var config = phe.cnc.config.Config.getInstance(); config.addListener('changeAudio', this.onChangeAudio, this); var battleground = ClientLib.Vis.VisMain.GetInstance().get_Battleground(); this.previousPlayAreaSoundVolume = battleground.get_SoundVolume(); battleground.set_SoundVolume(config.getAudioAmbientLevel() / 100); }, /** @inheritDoc */ deactivate: function(wasActive) { if (!wasActive || this.previousPlayAreaSoundVolume === null) { return; } phe.cnc.config.Config.getInstance().removeListener('changeAudio', this.onChangeAudio, this); ClientLib.Vis.VisMain.GetInstance().get_Battleground().set_SoundVolume(this.previousPlayAreaSoundVolume); this.previousPlayAreaSoundVolume = null; }, onChangeAudio: function() { phe.cnc.config.Config.getInstance().removeListener('changeAudio', this.onChangeAudio, this); this.previousPlayAreaSoundVolume = null; } } }); qx.Class.define('Tweaks.Feature.NotificationSidebarFix', { extend: qx.core.Object, implement: [Tweaks.Feature.IFeature], defer: function(statics, members) { Tweaks.getInstance().registerFeature(members.constructor, { category: Tweaks.Category.Bugfix, name: 'Fix notification sidebar error', description: 'Fixes a common script error caused by a bug in the notification sidebar. ' + '<a href="http://forum.alliances.commandandconquer.com/showthread.php?tid=32553" style="color:' + webfrontend.gui.util.BBCode.clrLink + ';" target="_blank">Read more</a>', configKey: 'NotificationSidebarFix', disabled: PerforceChangelist >= 425395 ? 'Obsolete. Bug fixed in patch 15.2' : false }); }, construct: function() { var source = webfrontend.gui.bars.NotificationBar.prototype._onNotificationUpdated.toString(); this.addToSidebarMethodName = source.match(/this\.([A-Za-z_]+)\([A-Za-z]+\);/)[1]; source = webfrontend.gui.bars.NotificationBar.prototype[this.addToSidebarMethodName].toString(); var matches = source.match(/this\.([A-Za-z_]+)\.removeAt\(webfrontend\.gui\.bars\.NotificationBar\.MaxNumberOfNotifications\);this\.([A-Za-z_]+)--;\}\s*;var [A-Za-z]+=this.([A-Za-z_]+)\([A-Za-z]+\);/); this.sidebarNotificationContainerMemberName = matches[1]; this.sidebarNotificationCountMemberName = matches[2]; this.createSidebarItemMethodName = matches[3]; source = webfrontend.gui.bars.NotificationBar.prototype._onNotificationRemoved.toString(); matches = source.match(/var ([A-Za-z]+)=([A-Za-z]+)\.get_Id\(\);var ([A-Za-z]+)=\2\.get_IdOnlineOnly\(\);if\(\1>0&&this\.([A-Za-z_]+)\[\1\]!=null\)\{.+?\}\s*else if\(\1==0&&this\.([A-Za-z_]+)\[\3\]!=null\)\{/); this.sidebarNotificationMapByIdMemberName = matches[4]; this.sidebarNotificationMapByIdOnlineOnlyMemberName = matches[5]; }, members: { addToSidebarMethodName: null, sidebarNotificationContainerMemberName: null, sidebarNotificationCountMemberName: null, createSidebarItemMethodName: null, sidebarNotificationMapByIdMemberName: null, sidebarNotificationMapByIdOnlineOnlyMemberName: null, removedNotifications: null, onRender: function(checkbox, label, config) {}, onReset: function(config) {}, onSaveConfig: function(config) {}, /** @inheritDoc */ activate: function(wasActive) { if (wasActive) { return; } var source = webfrontend.gui.bars.NotificationBar.prototype[this.addToSidebarMethodName].toString(); var rewrittenFunctionBody = source.replace( /while\(this\.[A-Za-z_]+>webfrontend\.gui\.bars\.NotificationBar\.MaxNumberOfNotifications\)\{.+?\}\s*;/, 'this.truncateSidebarNotifications(this);' ); var notificationBar = qx.core.Init.getApplication().getNotificationBar(); if (notificationBar.truncateSidebarNotifications === undefined) { notificationBar.truncateSidebarNotifications = this.truncateSidebarNotifications.bind(this); } notificationBar[this.addToSidebarMethodName] = eval('(' + rewrittenFunctionBody + ')'); this.removedNotifications = this.cleanupNotificationMap(notificationBar, notificationBar[this.sidebarNotificationMapByIdMemberName]) .concat(this.cleanupNotificationMap(notificationBar, notificationBar[this.sidebarNotificationMapByIdOnlineOnlyMemberName])); }, deactivate: function(wasActive) { if (!wasActive) { return; } var notificationBar = qx.core.Init.getApplication().getNotificationBar(); notificationBar[this.addToSidebarMethodName] = notificationBar.constructor.prototype[this.addToSidebarMethodName]; if (this.removedNotifications !== null) { for (var i = 0; i < this.removedNotifications.length; i++) { // Create sidebar items and add them to id maps, but don't add items to sidebar notificationBar[this.createSidebarItemMethodName](this.removedNotifications[i]); } this.removedNotifications = null; } }, /** * @param {webfrontend.gui.bars.NotificationBar} notificationBar * @param {Object} map * @returns {Array} */ cleanupNotificationMap: function(notificationBar, map) { var notificationContainer = notificationBar[this.sidebarNotificationContainerMemberName]; var removed = []; for (var id in map) { var sidebarItem = map[id]; if (sidebarItem !== null && notificationContainer.indexOf(sidebarItem) === -1) { var notification = this.getSidebarItemNotification(notificationBar, sidebarItem); if (notification !== null) { // Add missing items to sidebar so webfrontend.gui.bars.NotificationBar.prototype._onNotificationRemoved can remove them properly notificationContainer.add(sidebarItem); notificationBar[this.sidebarNotificationCountMemberName]++; notificationBar._onNotificationRemoved(notification); removed.push(notification); } } } return removed; }, /** * @param {webfrontend.gui.bars.NotificationBar} notificationBar */ truncateSidebarNotifications: function(notificationBar) { var notificationContainer = notificationBar[this.sidebarNotificationContainerMemberName]; while (notificationBar[this.sidebarNotificationCountMemberName] > webfrontend.gui.bars.NotificationBar.MaxNumberOfNotifications) { var sidebarItem = notificationContainer.getChildren()[webfrontend.gui.bars.NotificationBar.MaxNumberOfNotifications]; var notification = this.getSidebarItemNotification(notificationBar, sidebarItem); if (notification !== null) { notificationBar._onNotificationRemoved(notification); this.removedNotifications.push(notification); } else { notificationContainer.removeAt(webfrontend.gui.bars.NotificationBar.MaxNumberOfNotifications); notificationBar[this.sidebarNotificationCountMemberName]--; } } }, /** * @param {webfrontend.gui.bars.NotificationBar} notificationBar * @param {qx.ui.container.Composite} sidebarItem * @returns {ClientLib.Data.Notification} */ getSidebarItemNotification: function(notificationBar, sidebarItem) { var clickListeners = qx.event.Registration.getManager(sidebarItem).getListeners(sidebarItem, 'click'); for (var i = 0; i < clickListeners.length; i++) { if (clickListeners[i].handler === notificationBar._onClick) { return clickListeners[i].context.notification; } } return null; } } }); qx.Class.define('Tweaks.Feature.NotificationTickerFix', { extend: qx.core.Object, implement: [Tweaks.Feature.IFeature], defer: function(statics, members) { Tweaks.getInstance().registerFeature(members.constructor, { category: Tweaks.Category.Bugfix, name: 'Fix notification ticker error', description: 'Fixes a common script error caused by a bug in the notification ticker. ' + '<a href="http://forum.alliances.commandandconquer.com/showthread.php?tid=32553" style="color:' + webfrontend.gui.util.BBCode.clrLink + ';" target="_blank">Read more</a>', configKey: 'NotificationTickerFix', disabled: PerforceChangelist >= 436669 ? 'Obsolete. Bug fixed in patch 15.3' : false }); }, construct: function() { var source = webfrontend.gui.notifications.Ticker.prototype._onTick.toString(); var matches = source.match(/this\.([A-Za-z_]+)\.removeChild\(this\.([A-Za-z_]+)\[i\]\.getElement\(\)\);/); this.tickerDomContainerMemberName = matches[1]; this.tickerItemArrayMemberName = matches[2]; }, members: { tickerDomContainerMemberName: null, tickerItemArrayMemberName: null, removedTickerItems: null, onRender: function(checkbox, label, config) {}, onReset: function(config) {}, onSaveConfig: function(config) {}, /** @inheritDoc */ activate: function(wasActive) { if (wasActive) { return; } var ticker = qx.core.Init.getApplication().getMessagingTicker(); var domContainer = ticker[this.tickerDomContainerMemberName]; var tickerItems = ticker[this.tickerItemArrayMemberName]; if (domContainer === null) { ticker._onTick(); domContainer = ticker[this.tickerDomContainerMemberName]; } if (tickerItems.length > domContainer.children.length) { if (this.removedTickerItems === null) { this.removedTickerItems = []; } for (var i = tickerItems.length - 1; i >= 0; i--) { if (!domContainer.contains(tickerItems[i].getElement())) { this.removedTickerItems.push(tickerItems.splice(i, 1)[0]); } } } }, /** @inheritDoc */ deactivate: function(wasActive) { if (this.removedTickerItems === null) { return; } var ticker = qx.core.Init.getApplication().getMessagingTicker(); var tickerItems = ticker[this.tickerItemArrayMemberName]; // Shove back the errorneus notifications that were removed on activation. This will effectively return the ticker to its broken state. for (var i = 0; i < this.removedTickerItems.length; i++) { tickerItems.push(this.removedTickerItems[i]); } this.removedTickerItems = null; } } }); qx.Class.define('Tweaks.Feature.ReportsLoadFix', { extend: qx.core.Object, implement: [Tweaks.Feature.IFeature], defer: function(statics, members) { Tweaks.getInstance().registerFeature(members.constructor, { category: Tweaks.Category.Bugfix, name: 'Fix report load error', description: 'Fixes a common script error that occurs when opening a tab in the reports overlay. Also fixes a similar problem in the base info window causing reports not to load. ' + '<a href="http://forum.alliances.commandandconquer.com/showthread.php?tid=33706" style="color:' + webfrontend.gui.util.BBCode.clrLink + ';" target="_blank">Read more</a>', configKey: 'ReportsLoadFix' }); }, construct: function() { var source = ClientLib.Data.Reports.Reports.prototype.RequestReportHeaderDataAll.toString(); this.onResponseReportHeaderDataAllMethodName = source.match(/\(new \$I\.[A-Z]{6}\)\.[A-Z]{6}\(this,this\.([A-Z]{6})\)/)[1]; source = ClientLib.Data.Reports.Reports.prototype.RequestReportHeaderDataBase.toString(); this.onResponseReportHeaderDataBaseMethodName = source.match(/\(new \$I\.[A-Z]{6}\)\.[A-Z]{6}\(this,this\.([A-Z]{6})\)/)[1]; }, members: { onResponseReportHeaderDataAllMethodName: null, onResponseReportHeaderDataBaseMethodName: null, onRender: function(checkbox, label, config) {}, onReset: function(config) {}, onSaveConfig: function(config) {}, /** @inheritDoc */ activate: function(wasActive) { if (wasActive) { return; } var reports = ClientLib.Data.MainData.GetInstance().get_Reports(); var source = ClientLib.Data.Reports.Reports.prototype[this.onResponseReportHeaderDataAllMethodName].toString(); var rewrittenFunctionBody = source.replace( /(var ([A-Za-z]+)=)null;if\([A-Za-z]+\.length==[A-Za-z]+\)\{\2=(.+?\})\}/, '$1$3' ); reports[this.onResponseReportHeaderDataAllMethodName] = eval('(' + rewrittenFunctionBody + ')'); source = ClientLib.Data.Reports.Reports.prototype[this.onResponseReportHeaderDataBaseMethodName].toString(); rewrittenFunctionBody = source.replace( /if\([A-Za-z]+\.length==[A-Za-z]+\.NumReportsRequested\)\{(.+?\})\}/, '$1' ); reports[this.onResponseReportHeaderDataBaseMethodName] = eval('(' + rewrittenFunctionBody + ')'); }, /** @inheritDoc */ deactivate: function(wasActive) { if (!wasActive) { return; } var reports = ClientLib.Data.MainData.GetInstance().get_Reports(); reports[this.onResponseReportHeaderDataAllMethodName] = ClientLib.Data.Reports.Reports.prototype[this.onResponseReportHeaderDataAllMethodName]; reports[this.onResponseReportHeaderDataBaseMethodName] = ClientLib.Data.Reports.Reports.prototype[this.onResponseReportHeaderDataBaseMethodName]; } } }); qx.Class.define('Tweaks.Feature.PointerEvent', { extend: qx.core.Object, implement: [Tweaks.Feature.IFeature], defer: function(statics, members) { Tweaks.getInstance().registerFeature(members.constructor, { category: Tweaks.Category.Bugfix, name: 'Fix pointer event duplication', description: 'Fixes mouse clicks being handled twice. Most notably allows opening of the Scripts menu. ' + '<a href="http://forum.alliances.commandandconquer.com/showthread.php?tid=49151" style="color:' + webfrontend.gui.util.BBCode.clrLink + ';" target="_blank">Read more</a>', configKey: 'PointerEvent', disabled: statics.isBrowserAffected() ? false : 'Your browser is not affected' }); }, statics: { isBrowserAffected: function() { return 'PointerEvent' in window && !qx.bom.client.Event.getMsPointer(); } }, members: { onRender: function(checkbox, label, config) {}, onReset: function(config) {}, onSaveConfig: function(config) {}, /** @inheritDoc */ activate: function(wasActive) { if (!wasActive) { this.__getPointerEventHandler()._stopObserver(); } }, /** @inheritDoc */ deactivate: function(wasActive) { if (wasActive) { var handler = this.__getPointerEventHandler(); handler._initObserver(handler._onMouseEvent); } }, /** * @returns {qx.event.handler.Pointer} */ __getPointerEventHandler: function() { return qx.event.Registration.getManager(document).getHandler(qx.event.handler.Pointer); } } }); qx.Class.define('Tweaks.Feature.MaelstromToolsButtons', { extend: qx.core.Object, implement: [Tweaks.Feature.IFeature], defer: function(statics, members) { Tweaks.getInstance().registerFeature(members.constructor, { category: Tweaks.Category.Script, name: 'Fix MaelstromTools button logic', description: 'Tested with MaelstromTools 0.1.3.2', configKey: 'MaelstromToolsButtons' }); }, members: { fixRepairAllCheckbox: null, fixCollectPackagesCheckbox: null, wrapperInstalled: false, /** @inheritDoc */ onRender: function(checkbox, label, config) { var container = new qx.ui.container.Composite(new qx.ui.layout.VBox()); container.add(checkbox); container.add(this.fixRepairAllCheckbox = new qx.ui.form.CheckBox().set({ label: 'Don\'t show "Repair all" button for ghost bases', marginLeft: Tweaks.SettingsWindow.IndentStep, value: config.fixRepairAll || false })); container.add(this.fixCollectPackagesCheckbox = new qx.ui.form.CheckBox().set({ label: 'Don\'t show "Collect packages" button for ghost or locked bases', marginLeft: Tweaks.SettingsWindow.IndentStep, value: config.fixCollectPackages || false })); container.add(label); checkbox.bind('value', this.fixRepairAllCheckbox, 'enabled'); checkbox.bind('value', this.fixCollectPackagesCheckbox, 'enabled'); return container; }, /** @inheritDoc */ onReset: function(config) { this.fixRepairAllCheckbox.setValue(config.fixRepairAll || false); this.fixCollectPackagesCheckbox.setValue(config.fixCollectPackages || false); }, /** @inheritDoc */ onSaveConfig: function(config) { config.fixRepairAll = this.fixRepairAllCheckbox.getValue(); config.fixCollectPackages = this.fixCollectPackagesCheckbox.getValue(); }, /** @inheritDoc */ activate: function(wasActive) { if (this.wrapperInstalled) { return; } if (window.MaelstromTools === undefined) { // If MaelstromTools is not yet loaded, create a construct that activates the fixes once it has loaded var context = this; this.wrapperInstalled = true; window.MaelstromTools = { set Wrapper(value) { delete this.Wrapper; this.Wrapper = value; context.wrapperInstalled = false; context.applyFixes(false); return value; } }; } else { this.applyFixes(false); } }, /** @inheritDoc */ deactivate: function(wasActive) { if (wasActive) { if (this.wrapperInstalled) { delete window.MaelstromTools; this.wrapperInstalled = false; } else { this.applyFixes(true); } } }, /** * @param {Boolean} deactivating */ applyFixes: function(deactivating) { if (MaelstromTools.Wrapper.CanRepairAll_Original === undefined) { MaelstromTools.Wrapper.CanRepairAll_Original = MaelstromTools.Wrapper.CanRepairAll; } if (!deactivating && this.fixRepairAllCheckbox.getValue()) { MaelstromTools.Wrapper.CanRepairAll = function(city, viewMode) { if (city.get_IsGhostMode()) { return false; } return this.CanRepairAll_Original(city, viewMode); }; } else { MaelstromTools.Wrapper.CanRepairAll = MaelstromTools.Wrapper.CanRepairAll_Original; } if (!deactivating && this.fixCollectPackagesCheckbox.getValue()) { var source = MaelstromTools.Base.prototype.checkForPackages.toString(); var rewrittenFunctionBody = source.replace( /(([A-Za-z_]+)\.get_CityBuildingsData\(\)\.get_HasCollectableBuildings\(\))/, '!$2.get_IsGhostMode() && !$2.get_IsLocked() && $1' ).replace( /(MT_Cache)/, 'var $1 = MaelstromTools.Cache.getInstance(); $1' ); MaelstromTools.Base.getInstance().checkForPackages = eval('(' + rewrittenFunctionBody + ')'); } else { MaelstromTools.Base.getInstance().checkForPackages = MaelstromTools.Base.prototype.checkForPackages; } } } }); qx.Class.define('Tweaks.Feature.NotifyAboutNewFeatures', { extend: qx.core.Object, implement: [Tweaks.Feature.IFeature], defer: function(statics, members) { Tweaks.getInstance().registerFeature(members.constructor, { category: Tweaks.Category.Self, name: 'Notify me about new features', description: 'Displays an icon when new features are added and highlights them in this window.', configKey: 'NotifyAboutNewFeatures' }); }, members: { desktopButton: null, alteredContainers: null, newFeatureCount: 0, onRender: function(checkbox, label, config) {}, onReset: function(config) {}, onSaveConfig: function(config) {}, /** @inheritDoc */ activate: function(wasActive) { if (wasActive) { return; } var core = Tweaks.getInstance(); core.addListener('addFeature', this.onFeatureAdd, this); core.addListener('saveSettings', this.onSettingsChange, this); if (!core.initialized) { core.addListenerOnce('initialize', this.onSettingsChange, this); } }, /** @inheritDoc */ deactivate: function(wasActive) { if (wasActive) { if (this.desktopButton !== null) { this.desktopButton.exclude(); } var core = Tweaks.getInstance(); core.removeListener('addFeature', this.onFeatureAdd, this); core.removeListener('saveSettings', this.onSettingsChange, this); this.restoreContainers(); } }, /** * @param {qx.event.type.Data} event */ onFeatureAdd: function(event) { var details = event.getData(); if (!Tweaks.getInstance().hasConfig(details.construct)) { this.highlightContainer(details.container); this.newFeatureCount++; var desktopButton = this.getDesktopButton(); desktopButton.setLabel(this.newFeatureCount.toString()); desktopButton.show(); } }, onSettingsChange: function() { this.restoreContainers(); var core = Tweaks.getInstance(); var features = core.getAllFeatures(); this.newFeatureCount = 0; for (var id in features) { var feature = features[id]; if (!core.hasConfig(feature.construct) && !feature.options.disabled) { this.highlightContainer(feature.container); this.newFeatureCount++; } } if (this.newFeatureCount > 0) { var desktopButton = this.getDesktopButton(); desktopButton.setLabel(this.newFeatureCount.toString()); desktopButton.show(); } else if (this.desktopButton !== null) { this.desktopButton.exclude(); } }, onClickDesktopButton: function() { Tweaks.getInstance().openSettingsWindow(); }, /** * @returns {Tweaks.NotificationButton} */ getDesktopButton: function() { if (this.desktopButton === null) { this.desktopButton = new Tweaks.NotificationButton().set({ appearance: 'button-standard-gdi', icon: 'webfrontend/ui/icons/icn_show_combat_active.png', toolTipText: 'New features in Tweaks' }); this.desktopButton.getChildControl('icon').set({ scale: true, width: 36, height: 32 }); this.desktopButton.addListener('execute', this.onClickDesktopButton, this); qx.core.Init.getApplication().getDesktop().add(this.desktopButton, { right: 125, top: 90 }); } return this.desktopButton; }, /** * @param {qx.ui.container.Composite} container */ highlightContainer: function(container) { if (this.alteredContainers === null) { this.alteredContainers = []; } var containerElement = (PerforceChangelist >= 430398) ? container.getContentElement() : container.getContainerElement(); this.alteredContainers.push({ container: container, backgroundColor: container.getBackgroundColor(), marginLeft: container.getMarginLeft(), paddingLeft: container.getPaddingLeft(), textColor: container.getTextColor(), borderRadius: containerElement.getStyle('border-radius') }); container.set({ backgroundColor: '#3c7c3c', marginLeft: container.getMarginLeft() - 4, paddingLeft: container.getPaddingLeft() + 4, textColor: '#333' }); containerElement.setStyle('border-radius', '8px'); }, restoreContainers: function() { if (this.alteredContainers === null) { return; } for (var i = 0; i < this.alteredContainers.length; i++) { var info = this.alteredContainers[i]; info.container.set({ backgroundColor: info.backgroundColor, marginLeft: info.marginLeft, paddingLeft: info.paddingLeft, textColor: info.textColor }); var containerElement = PerforceChangelist >= 430398 ? info.container.getContentElement() : info.container.getContainerElement(); containerElement.setStyle('border-radius', info.borderRadius); } this.alteredContainers = null; } } }); qx.Class.define('Tweaks.Feature.PlayerBasePlateColoring', { extend: qx.core.Object, implement: [Tweaks.Feature.IFeature], defer: function(statics, members) { Tweaks.getInstance().registerFeature(members.constructor, { category: Tweaks.Category.Script, name: 'Enable player base plate coloring', description: 'Allows scripts like TACS to color player base plates.', configKey: 'PlayerBasePlateColoring' }); }, construct: function() { var regionCity$ctorMemberName = null; for (var key in ClientLib.Vis.Region.RegionCity.prototype) { if (typeof ClientLib.Vis.Region.RegionCity.prototype[key] === 'function' && ClientLib.Vis.Region.RegionCity.prototype[key].toString().indexOf('region_city_owner') !== -1) { regionCity$ctorMemberName = key; break; } } if (regionCity$ctorMemberName === null) { throw new Error('Unable to locate ClientLib.Vis.Region.RegionCity.prototype.$ctor'); } var source = ClientLib.Vis.Region.RegionCity.prototype[regionCity$ctorMemberName].toString(); var matches = source.match(PerforceChangelist >= 443425 ? /this\.([A-Z]{6})=\(new \$I\.([A-Z]{6})\)\.([A-Z]{6})\(h, this\.[A-Z]{6}, this\.[A-Z]{6}, this\.[A-Z]{6}\);/ : /this\.([A-Z]{6})=\(new \$I\.([A-Z]{6})\)\.([A-Z]{6})\(\$I\.[A-Z]{6}\.Black/ ); var basePlateMemberName = matches[1]; this.playerBasePlateClassName = matches[2]; var playerBasePlate$ctorMemberName = matches[3]; if (typeof ClientLib.Vis.Region.RegionCity.prototype.get_BasePlate !== 'function') { ClientLib.Vis.Region.RegionCity.prototype.get_BasePlate = eval('(function(){return this.' + basePlateMemberName + ';})'); } source = $I[this.playerBasePlateClassName].prototype[playerBasePlate$ctorMemberName].toString(); matches = source.match(/\$I\.([A-Z]{6})\.prototype\.([A-Z]{6})\.call/); var basePlateClassName = matches[1]; var basePlate$ctorMemberName = matches[2]; source = $I[basePlateClassName].prototype[basePlate$ctorMemberName].toString(); matches = source.match(/\$I\.([A-Z]{6})\.prototype\.([A-Z]{6})\.call/); var parentBasePlateClassName = matches[1]; var parentBasePlate$ctorMemberName = matches[2]; source = $I[parentBasePlateClassName].prototype[parentBasePlate$ctorMemberName].toString(); matches = source.match(/this\.([A-Z]{6})=a.+this\.([A-Z]{6})\(\)/); var plateColorMemberName = matches[1]; var initMethodName = matches[2]; if (typeof $I[this.playerBasePlateClassName].prototype.setPlateColor !== 'function') { $I[parentBasePlateClassName].prototype.setPlateColor = eval('(function(a){this.' + plateColorMemberName + '=a;this.' + initMethodName + '();})'); } source = ClientLib.Vis.Region.RegionCity.prototype.VisUpdate.toString(); this.playerBasePlateUpdateMethodName = source.match(/this\.[A-Z]{6}\.([A-Z]{6})\(this\.[A-Z]{6},this\.[A-Z]{6}\);/)[1]; source = $I[this.playerBasePlateClassName].prototype[this.playerBasePlateUpdateMethodName].toString(); var rewrittenFunctionBody = source.replace( /\$I\.[A-Z]{6}\.Black/, 'this.' + plateColorMemberName ); this.rewrittenPlayerbasePlateUpdateMethod = eval('(' + rewrittenFunctionBody + ')'); }, members: { playerBasePlateClassName: null, playerBasePlateUpdateMethodName: null, rewrittenPlayerbasePlateUpdateMethod: null, originalPlayerbasePlateUpdateMethod: null, onRender: function(checkbox, label, config) {}, onReset: function(config) {}, onSaveConfig: function(config) {}, /** @inheritDoc */ activate: function(wasActive) { if (wasActive) { return; } this.originalPlayerbasePlateUpdateMethod = $I[this.playerBasePlateClassName].prototype[this.playerBasePlateUpdateMethodName]; $I[this.playerBasePlateClassName].prototype[this.playerBasePlateUpdateMethodName] = this.rewrittenPlayerbasePlateUpdateMethod; }, /** @inheritDoc */ deactivate: function(wasActive) { if (!wasActive || this.originalPlayerbasePlateUpdateMethod === null) { return; } $I[this.playerBasePlateClassName].prototype[this.playerBasePlateUpdateMethodName] = this.originalPlayerbasePlateUpdateMethod; } } }); } function waitForGame() { try { if (typeof qx !== 'undefined' && qx.core.Init.getApplication() && qx.core.Init.getApplication().initDone) { createTweaks(); Tweaks.getInstance().initialize(); } else { setTimeout(waitForGame, 1000); } } catch (e) { console.log('Tweaks: ', e.toString()); } } setTimeout(waitForGame, 1000); }; var script = document.createElement('script'); script.innerHTML = '(' + main.toString() + ')();'; script.type = 'text/javascript'; document.getElementsByTagName('head')[0].appendChild(script); })();