for us
55 | if (typeof (window.onresize) == "function")
56 | window.onresize();
57 | }
58 |
59 | function onContentLoaded() {
60 | if ($("#esreality").length)
61 | return;
62 |
63 | var fixedElementsHeight = 87;
64 |
65 | extraQL.addStyle(
66 | "#esreality { width: 300px; color: black; background-color: white; display: none; }",
67 | "#esrealityHeader { border-bottom: 1px solid #e8e8e8; padding: 9px 9px 8px 9px; }",
68 | "#esrealityHeader .headerText { font-size: 14px; font-weight: bold; line-height: 18px; }",
69 | "#esrealityHeader a { color: #A0220B; font-size: 14px; }",
70 | "#esrealityDetails { height: 32px; overflow: hidden; padding: 9px 6px; border-bottom: 1px solid #e8e8e8; }",
71 | "#esrealityContent { height: " + (550 - fixedElementsHeight) + "px; overflow: auto; }",
72 | "#esrealityContent div { padding: 3px 6px; max-height: 26px; overflow: hidden; }",
73 | "#esrealityContent .active { background-color: #ccc; }",
74 | "#esrealityContent a { color: black; text-decoration: none; }",
75 | "#esrealityContent a:hover { text-decoration: underline; }"
76 | );
77 |
78 | var content =
79 | "
" +
80 | " " +
81 | "
" +
82 | "
" +
83 | "
";
84 | extraQL.addTabPage("esreality", "ESR", content, undefined, 300);
85 |
86 | $("#esrealityShowForums").click(function () {
87 | $("#esrealityHeader a").removeClass("active");
88 | $(this).addClass("active");
89 | updateForums();
90 | });
91 |
92 | updateForums();
93 | }
94 |
95 | function updateForums() {
96 | if (quakelive.IsGameRunning())
97 | return;
98 |
99 | if (updateTimeoutHandle)
100 | window.clearTimeout(updateTimeoutHandle);
101 |
102 | $.ajax({
103 | url: extraQL.BASE_URL + "proxy",
104 | data: { url: URL_FORUM },
105 | dataType: "text"
106 | })
107 | .done(parseForum)
108 | .fail(function() { extraQL.log("Could not load esreality forum"); });
109 |
110 | updateTimeoutHandle = window.setTimeout(updateForums, UPDATE_INTERVAL);
111 | }
112 |
113 | function parseForum(html) {
114 | try {
115 | var $forum = $("#esrealityContent");
116 | $forum.empty();
117 |
118 | // update post list
119 | $(html).find(".pl_row").each(function (i, item) {
120 | var $row = $(item);
121 | var $this = $row.children(".pl_main");
122 | var $link = $this.children("span").children("a:first-child");
123 | var lastCommentTime = $this.children("div").text();
124 | var replies = $row.find(".pl_replies>div").text().trim();
125 | var author = $row.find(".pl_author>div>a").text();
126 | var descr = "Author:
" + extraQL.escapeHtml(author) + ", Replies: " + replies + "
" + lastCommentTime;
127 | $forum.append("
");
131 | });
132 | $("#esrealityContent div").hover(showForumDetails);
133 |
134 | showDetailsForFirstEntry();
135 | } catch (e) {
136 | extraQL.log(e);
137 | }
138 | }
139 |
140 | function showDetailsForFirstEntry() {
141 | var first = $("#esrealityContent>div")[0];
142 | if (!first) {
143 | $("#esrealityStatus").text("");
144 | } else {
145 | showForumDetails.apply(first);
146 | $(first).addClass("active");
147 | }
148 | }
149 |
150 | function showForumDetails() {
151 | var $this = $(this);
152 | $("#esrealityDetails").html($this.data("status"));
153 | $("#esrealityContent div").removeClass("active");
154 | $this.addClass("active");
155 | }
156 |
157 |
158 | init();
159 | })();
--------------------------------------------------------------------------------
/source/scripts/attic/extGraphHist.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id 134693
3 | // @name Quake Live Extended Graph History
4 | // @version 0.18
5 | // @namespace phob.net
6 | // @author wn
7 | // @description Keep more of your match history!
8 | // @include http://*.quakelive.com/*
9 | // @exclude http://*.quakelive.com/forum*
10 | // @run-at document-end
11 | // @updateURL https://userscripts.org/scripts/source/134693.meta.js
12 | // ==/UserScript==
13 |
14 |
15 | /**
16 | * Set up some stuff for user script updating
17 | */
18 | var SCRIPT_NAME = "Quake Live Extended Graph History"
19 | , SCRIPT_VER = "0.18";
20 | GM_updatingEnabled = "GM_updatingEnabled" in window ? GM_updatingEnabled : false;
21 |
22 |
23 | /**
24 | * Based on:
25 | * GM_ API emulation for Chrome; 2009, 2010 James Campos
26 | * cc-by-3.0; http://creativecommons.org/licenses/by/3.0/
27 | */
28 | if (window.chrome) {
29 | GM_getValue = function (aName, aDefaultValue) {
30 | var value = localStorage.getItem(aName);
31 | if (!value) return aDefaultValue;
32 | var type = value[0];
33 | value = value.substring(1);
34 | switch (type) {
35 | case "b":
36 | return value == "true";
37 | case "n":
38 | return Number(value);
39 | default:
40 | return value;
41 | }
42 | }
43 | GM_setValue = function (aName, aValue) {
44 | aValue = (typeof aValue)[0] + aValue;
45 | localStorage.setItem(aName, aValue);
46 | }
47 | GM_registerMenuCommand = function () { };
48 | }
49 |
50 |
51 | /**
52 | * Use an auto-update script if integrated updating isn't enabled
53 | * http://userscripts.org/scripts/show/38017
54 | * NOTE: Modified to add the new version number to the upgrade prompt
55 | * and custom messages for Chrome users (requires a manual update).
56 | */
57 | if (!GM_updatingEnabled) {
58 | // var AutoUpdater_134693 = { id: 134693, days: 1, name: SCRIPT_NAME, version: SCRIPT_VER, time: new Date().getTime(), call: function (response, secure) { GM_xmlhttpRequest({ method: "GET", url: "http" + (secure ? "s" : "") + "://userscripts.org/scripts/source/" + this.id + ".meta.js", onload: function (xpr) { AutoUpdater_134693.compare(xpr, response) }, onerror: function (xpr) { if (secure) { AutoUpdater_134693.call(response, false) } } }) }, enable: function () { GM_registerMenuCommand(this.name + ": Enable updates", function () { GM_setValue("updated_134693", new Date().getTime() + ""); AutoUpdater_134693.call(true, true) }) }, compareVersion: function (r_version, l_version) { var r_parts = r_version.split("."), l_parts = l_version.split("."), r_len = r_parts.length, l_len = l_parts.length, r = l = 0; for (var i = 0, len = (r_len > l_len ? r_len : l_len) ; i < len && r == l; ++i) { r = +(r_parts[i] || "0"); l = +(l_parts[i] || "0") } return (r !== l) ? r > l : false }, compare: function (xpr, response) { this.xversion = /\/\/\s*@version\s+(.+)\s*\n/i.exec(xpr.responseText); this.xname = /\/\/\s*@name\s+(.+)\s*\n/i.exec(xpr.responseText); if ((this.xversion) && (this.xname[1] == this.name)) { this.xversion = this.xversion[1]; this.xname = this.xname[1] } else { if ((xpr.responseText.match("the page you requested doesn't exist")) || (this.xname[1] != this.name)) { GM_setValue("updated_134693", "off") } return false } var updated = this.compareVersion(this.xversion, this.version); if (updated && confirm("A new version of " + this.xname + " is available.\nDo you wish to install the latest version (" + this.xversion + ")?")) { var path = "http://userscripts.org/scripts/source/" + this.id + ".user.js"; if (window.chrome) { prompt("This script can't be updated automatically in Chrome.\nPlease uninstall the old version, and navigate to the URL provided below.", path) } else { try { window.parent.location.href = path } catch (e) { } } } else { if (this.xversion && updated) { if (confirm("Do you want to turn off auto updating for this script?")) { GM_setValue("updated_134693", "off"); this.enable(); if (window.chrome) { alert("You will need to reinstall this script to enable auto-updating.") } else { alert("Automatic updates can be re-enabled for this script from the User Script Commands submenu.") } } } else { if (response) { alert("No updates available for " + this.name) } } } }, check: function () { if (GM_getValue("updated_134693", 0) == "off") { this.enable() } else { if (+this.time > (+GM_getValue("updated_134693", 0) + 1000 * 60 * 60 * 24 * this.days)) { GM_setValue("updated_134693", this.time + ""); this.call(false, true) } GM_registerMenuCommand(this.name + ": Check for updates", function () { GM_setValue("updated_134693", new Date().getTime() + ""); AutoUpdater_134693.call(true, true) }) } } }; AutoUpdater_134693.check();
59 | }
60 |
61 |
62 | // Source: http://wiki.greasespot.net/Content_Script_Injection
63 | function contentEval(source) {
64 | // Check for function input.
65 | if ("function" == typeof (source)) {
66 | source = "(" + source + ")();";
67 | }
68 |
69 | // Create a script node holding this source code.
70 | var script = document.createElement("script");
71 | script.setAttribute("type", "application/javascript");
72 | script.textContent = source;
73 |
74 | // Insert the script node into the page, so it will run, and immediately
75 | // remove it to clean up.
76 | document.body.appendChild(script);
77 | document.body.removeChild(script);
78 | }
79 |
80 |
81 | contentEval(function () {
82 |
83 | // Okay to run?
84 | if ("object" != typeof quakelive) {
85 | return;
86 | }
87 |
88 |
89 | var qlegh = {};
90 |
91 |
92 | /**
93 | * Order by game time (newest first)
94 | */
95 | qlegh.gtSort = function (a, b) {
96 | return parseInt(b.GAME_TIME) - parseInt(a.GAME_TIME);
97 | }
98 |
99 |
100 | /**
101 | * Determine whether mod_profile.InitGraphs will handle the update
102 | */
103 | qlegh.okayToUpdate = function () {
104 | return ("profile" != quakelive.pathParts[0] && "statistics" != quakelive.pathParts[1]);
105 | }
106 |
107 |
108 | /**
109 | * Get fresh match data
110 | */
111 | qlegh.getMatchHistory = function () {
112 | if (!qlegh.okayToUpdate()) {
113 | return;
114 | }
115 | $.ajax({
116 | type: "GET",
117 | url: "/profile/statistics/" + quakelive.username
118 | }).done(function (data) {
119 | qlegh.syncMatchHistory($(data).find("#prf_matchjson").text(), true);
120 | });
121 | }
122 |
123 |
124 | // Update on the initial load (delayed 5 seconds)
125 | setTimeout(function () {
126 | if (qlegh.okayToUpdate()) {
127 | qlegh.getMatchHistory();
128 | }
129 | }, 5E3);
130 |
131 | // Update whenever the game exits
132 | quakelive.AddHook("OnGameExited", function () {
133 | if (!qlegh.okayToUpdate()) {
134 | return;
135 | }
136 | qlegh.getMatchHistory();
137 | });
138 |
139 |
140 | /**
141 | * Sync up history and available stats
142 | * @param {String} stats The JSON string for the array of match objects
143 | * @param {Boolean} auto Whether the call was initiated by the script
144 | */
145 | qlegh.syncMatchHistory = function (stats, auto) {
146 | var history, newStats = [];
147 | auto = auto || false;
148 |
149 | try {
150 | stats = JSON.parse(stats);
151 | }
152 | catch (e) {
153 | // If called by InitGraphs...
154 | if (!auto) {
155 | oldInitGraphs.call(quakelive.mod_profile);
156 | }
157 | return;
158 | }
159 |
160 | // Get old stats
161 | history = amplify.store("qlegh_matchHistory") || [];
162 |
163 | // If we have no history, just use the available stats
164 | if (0 == history.length) {
165 | history = stats;
166 | }
167 |
168 | // Sanity check... newest games first
169 | history.sort(qlegh.gtSort);
170 | stats.sort(qlegh.gtSort);
171 |
172 | // Prepend newer stats to the history
173 | for (var i = 0, e = stats.length, h0 = parseInt(history[0].GAME_TIME) ; i < e; ++i) {
174 | if (parseInt(stats[i].GAME_TIME) > h0) {
175 | newStats.push(stats[i]);
176 | }
177 | else {
178 | break;
179 | }
180 | }
181 | history = newStats.reverse().concat(history);
182 |
183 | // Another sanity check... no duplicates
184 | var times = {};
185 | for (var i = 0, e = history.length; i < e; ++i) {
186 | if (!(history[i].GAME_TIME in times)) {
187 | times[history[i].GAME_TIME] = true;
188 | }
189 | else {
190 | history.splice(i, 1);
191 | e--;
192 | i--;
193 | }
194 | }
195 |
196 | // Only keep the 50 most recent matches (and sort them again)
197 | // NOTE: Over 50 would be possible, but the bars get quite thin.
198 | history = history.splice(0, Math.min(50, history.length)).sort(qlegh.gtSort);
199 | amplify.store("qlegh_matchHistory", history);
200 |
201 | // If called by InitGraphs...
202 | if (!auto) {
203 | // Save the result back to a JSON string
204 | $("#prf_matchjson").text(JSON.stringify(history));
205 |
206 | // Call the original function to initialize the graphs
207 | oldInitGraphs.call(quakelive.mod_profile);
208 | }
209 | }
210 |
211 |
212 | /**
213 | * Override InitGraphs to add the available match data to the history
214 | */
215 | var oldInitGraphs = quakelive.mod_profile.InitGraphs;
216 | quakelive.mod_profile.InitGraphs = function () {
217 | // Only handle the user's own profile
218 | if ($("#prf_player_name").text().toLowerCase() != quakelive.username.toLowerCase()) {
219 | oldInitGraphs.call(quakelive.mod_profile);
220 | return;
221 | }
222 |
223 | qlegh.syncMatchHistory($("#prf_matchjson").text());
224 | };
225 |
226 | }); // end of call to contentEval
227 |
--------------------------------------------------------------------------------
/source/scripts/attic/gametype.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Quake Live Game Type Picker
3 | // @version 1.3
4 | // @author PredatH0r
5 | // @description Select game-type and other server filters without opening the customization area
6 | // @include http://*.quakelive.com/*
7 | // @exclude http://*.quakelive.com/forum*
8 | // @unwrap
9 | // ==/UserScript==
10 |
11 | /*
12 | This script replaces the large "Public Matches" caption on the server browser page
13 | with quick customization items for gametype and match visibility
14 |
15 | Version 1.3
16 | - updated extraQL script url to sourceforge
17 |
18 | Version 1.1
19 | - added "Show filter panel" link
20 | */
21 |
22 | (function() {
23 | // external variables
24 | var quakelive = window.quakelive;
25 | var extraQL = window.extraQL;
26 |
27 | var _GAME_TYPES = [
28 | ["any", "any", 'tdm', 'All', 'All Game Types'],
29 | [2, 8, 'ffa', 'FFA', 'Free For All'],
30 | [4, 14, 'ca', 'CA', 'Clan Arena'],
31 | [7, 7, 'duel', 'Duel', 'One On One'],
32 | [6, 11, 'tdm', 'TDM', 'Team Deathmatch'],
33 | [3, 9, 'ctf', 'CTF', 'Capture The Flag'],
34 | [5, 10, 'ft', 'FT', 'Freeze Tag'],
35 | [16, 21, 'fctf', '1CTF', '1-Flag CTF'],
36 | [18, 23, 'ad', 'A&D', 'Attack & Defend'],
37 | [15, 20, 'dom', 'DOM', 'Domination'],
38 | [17, 22, 'harvester', 'HAR', 'Harvester'],
39 | [19, 24, 'rr', 'RR', 'Red Rover'],
40 | [25, 25, 'race', 'Race', 'Race Mode']
41 | ];
42 | var gameTypes = []; // transformed version of _GAME_TYPES: array of { index, regular, instagib, icon, text, hint }
43 |
44 | var currentGameTypeIndex = 0;
45 | var isInstagib = 0;
46 |
47 | function init() {
48 | if (extraQL.isOldUi) {
49 | extraQL.log("Sorry, but gametype.js is not compatible with this version of the QL UI.");
50 | return;
51 | }
52 |
53 | $.each(_GAME_TYPES, function(i, gameType) {
54 | gameTypes.push({ index: i, regular: gameType[0], instagib: gameType[1], icon: gameType[2], text: gameType[3], hint: gameType[4] });
55 | });
56 | extraQL.addStyle(
57 | "#gameTypeSwitcher { color: black; margin-left: 12px; font-family: Arial; display: inline-block; }",
58 | "#gameTypeSwitcher1 .gametype { display: inline-block; margin-right: 16px; }",
59 | "#gameTypeSwitcher1 a { color: inherit; text-decoration: none; }",
60 | "#gameTypeSwitcher1 a.active { color: #CC220B; text-decoration: underline; }",
61 | "#gameTypeSwitcher1 img { float: left; margin-right: 3px; width: 16px; height: 16px; }",
62 | "#gameTypeSwitcher1 span { vertical-align: middle; }",
63 | "#gameTypeSwitcher2 { margin: 3px 0; color: black; }",
64 | "#gameTypeSwitcher2 input { vertical-align: middle; }",
65 | "#gameTypeSwitcher2 label { margin: 0 20px 0 3px; vertical-align: middle; }",
66 | "#gameTypeSwitcher2 a { margin-left: 100px; color: black; text-decoration: underline; cursor: pointer; }",
67 | "#quickPublic { margin-left: 50px; }"
68 | );
69 | quakelive.AddHook("OnContentLoaded", onContentLoaded);
70 | }
71 |
72 | function onContentLoaded() {
73 | if (!quakelive.activeModule || quakelive.activeModule.GetTitle() != "Home")
74 | return;
75 |
76 | var $matchlistHeader = $("#matchlist_header_text");
77 | if ($matchlistHeader.length == 0) return;
78 | $matchlistHeader.empty();
79 | $matchlistHeader.css({ "display": "none", "height": "auto" });
80 |
81 | $matchlistHeader = $("#matchlist_header");
82 |
83 | var html1 = "";
84 | $.each(gameTypes, function(i, gameType) {
85 | html1 += "
";
87 | });
88 | var html2 = "
"
89 | + "
"
90 | + "
"
91 | + "
"
92 | + "
"
93 | + "
";
94 | $matchlistHeader.prepend(
95 | "
"
96 | + "
" + html1 + "
"
97 | + "
" + html2 + "
"
98 | +"
"
99 | );
100 | $("#matchlist_header_controls").css({ "width": "auto", "float": "right" });
101 |
102 | $("#gameTypeSwitcher1 a").click(function () {
103 | currentGameTypeIndex = $(this).data("index");
104 | updateCustomizationFormGameType();
105 | });
106 |
107 | $("#quickInsta").change(function() {
108 | isInstagib = $(this).prop("checked");
109 | updateCustomizationFormGameType();
110 | });
111 |
112 | $("#quickFriends").change(function () {
113 | $("#ctrl_filter_social").val($("#quickFriends").prop("checked") ? "friends" : "any").trigger("chosen:updated").trigger("change");
114 | });
115 |
116 | $("#quickPrem").change(function () {
117 | $("#premium_only").prop("checked", $("#quickPrem").prop("checked")).trigger("change");
118 | });
119 |
120 | $("#gameTypeSwitcher2 input:radio").click(function () {
121 | var privateValue = $(this).prop("value");
122 | $("#publicServer").parent().find("input:radio[value=" + privateValue + "]").prop("checked", "true").trigger("click");
123 | });
124 |
125 | parseCustomizationForm();
126 | }
127 |
128 | function parseCustomizationForm() {
129 | var $field = $("#ctrl_filter_gametype");
130 | var currentGameTypeValue = $field.val();
131 |
132 | currentGameTypeIndex = -1;
133 | isInstagib = false;
134 | $.each(gameTypes, function (i, gameType) {
135 | if (gameType.regular == currentGameTypeValue || gameType.instagib == currentGameTypeValue) {
136 | currentGameTypeIndex = i;
137 | isInstagib = currentGameTypeValue != gameType.regular;
138 | }
139 | });
140 | $("#quickPrem").prop("checked", $("#premium_only").prop("checked"));
141 | var privateValue = $("#publicServer").parent().find("input:radio:checked").val();
142 | $("#gameTypeSwitcher2").find("input:radio[value=" + privateValue + "]").prop("checked", "true");
143 | highlightActiveGametype();
144 | }
145 |
146 | function updateCustomizationFormGameType() {
147 | if (currentGameTypeIndex < 0) return;
148 | var gameType = gameTypes[currentGameTypeIndex];
149 | $("#ctrl_filter_gametype").val(isInstagib ? gameType.instagib : gameType.regular).trigger("chosen:updated").trigger("change");
150 | highlightActiveGametype();
151 | }
152 |
153 | function highlightActiveGametype() {
154 | $("#gameTypeSwitcher1 .gametype .active").removeClass("active");
155 | $("#gameTypeSwitcher1 .gametype [data-index=" + currentGameTypeIndex + "]").addClass("active");
156 | var gameType = currentGameTypeIndex >= 0 ? gameTypes[currentGameTypeIndex] : null;
157 | var allowInsta = gameType && gameType.instagib != gameType.regular;
158 | $("#quickInsta").prop("disabled", !allowInsta);
159 | $("#quickInsta").prop("checked", allowInsta && isInstagib);
160 | }
161 |
162 | init();
163 | })();
--------------------------------------------------------------------------------
/source/scripts/attic/irc.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id 186820
3 | // @name Quake Live IRC Link
4 | // @version 1.3
5 | // @author PredatH0r
6 | // @description Adds a link to the QuakeNet.org IRC Web Chat to your chat window
7 | // @unwrap
8 | // ==/UserScript==
9 |
10 | /*
11 |
12 | Version 1.3
13 | - added workaround for external links in QL Steam build
14 |
15 | Version 1.2
16 | - ensuring consistent order of tabs in the chat bar
17 |
18 | Version 1.1
19 | - updated extraQL script url to sourceforge
20 |
21 | */
22 |
23 | (function () {
24 | // external global variables
25 | var quakelive = window.quakelive;
26 | var extraQL = window.extraQL;
27 |
28 | var URL_IRC = "http://webchat.quakenet.org/?nick=" + quakelive.username + "&channels=quakelive&prompt=1";
29 |
30 | function init() {
31 | // delay init so that twitch, twitter, ESR and IRC scripts add items to chat menu bar in a defined order
32 | if (extraQL.hookVersion) // introduced at the same time as the addTabPage() "priority" param
33 | delayedInit();
34 | else
35 | setTimeout(delayedInit, 2400);
36 | }
37 |
38 | function delayedInit() {
39 | onContentLoaded();
40 | //extraQL.addStyle("#tab_irc { float: right; }");
41 | quakelive.AddHook("OnContentLoaded", onContentLoaded);
42 | }
43 |
44 | function onContentLoaded() {
45 | if ($("#tab_irc").length)
46 | return;
47 | extraQL.addTabPage("irc", "IRC", "", function() {
48 | quakelive.OpenURL(URL_IRC);
49 | window.event.stopPropagation();
50 | }, 400);
51 | }
52 |
53 | init();
54 | })();
--------------------------------------------------------------------------------
/source/scripts/attic/joinGame.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id joinGame
3 | // @name Join Game Through HTTP Link
4 | // @version 1.0
5 | // @author PredatH0r
6 | // @description Joins a game through a link of the format http://127.0.0.1:27963/join/91.198.152.211:27003/passwd
7 | // @unwrap
8 | // ==/UserScript==
9 |
10 | (function () {
11 | var hInterval;
12 |
13 | function init() {
14 | if (!extraQL || !extraQL.isLocalServerRunning())
15 | return;
16 |
17 | quakelive.AddHook("OnGameModeEnded", startPolling);
18 | startPolling();
19 | }
20 |
21 | function startPolling() {
22 | hInterval = window.setInterval(pollJoinInformation, 1000);
23 | }
24 |
25 | function pollJoinInformation() {
26 | $.getJSON(extraQL.BASE_URL + "join", function(info) {
27 | if (info.server) {
28 | window.clearTimeout(hInterval);
29 | if (info.pass)
30 | quakelive.cvars.Set("password", info.pass);
31 | window.join_server(info.server, undefined, info.pass);
32 | }
33 | });
34 | }
35 |
36 | init();
37 | })();
--------------------------------------------------------------------------------
/source/scripts/attic/keyboardNav.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id 186881
3 | // @name Quake Live Keyboard Navigation
4 | // @version 0.16
5 | // @namespace phob.net
6 | // @author wn
7 | // @description Adds some browser navigation functionality to the QUAKE LIVE standalone client
8 | // @include http://*.quakelive.com/*
9 | // @exclude http://*.quakelive.com/forum*
10 | // @run-at document-end
11 | // @updateURL https://userscripts.org/scripts/source/186881.meta.js
12 | // ==/UserScript==
13 |
14 | ////////////////////////////////////////////////////////////////////////////////////////////////////
15 | // DEPENDENCIES
16 | ////////////////////////////////////////////////////////////////////////////////////////////////////
17 |
18 | /* mousetrap v1.4.6 craig.is/killing/mice */
19 | !("Mousetrap" in window) && (function (J, r, f) {
20 | function s(a, b, d) { a.addEventListener ? a.addEventListener(b, d, !1) : a.attachEvent("on" + b, d) } function A(a) { if ("keypress" == a.type) { var b = String.fromCharCode(a.which); a.shiftKey || (b = b.toLowerCase()); return b } return h[a.which] ? h[a.which] : B[a.which] ? B[a.which] : String.fromCharCode(a.which).toLowerCase() } function t(a) { a = a || {}; var b = !1, d; for (d in n) a[d] ? b = !0 : n[d] = 0; b || (u = !1) } function C(a, b, d, c, e, v) {
21 | var g, k, f = [], h = d.type; if (!l[a]) return []; "keyup" == h && w(a) && (b = [a]); for (g = 0; g < l[a].length; ++g) if (k =
22 | l[a][g], !(!c && k.seq && n[k.seq] != k.level || h != k.action || ("keypress" != h || d.metaKey || d.ctrlKey) && b.sort().join(",") !== k.modifiers.sort().join(","))) { var m = c && k.seq == c && k.level == v; (!c && k.combo == e || m) && l[a].splice(g, 1); f.push(k) } return f
23 | } function K(a) { var b = []; a.shiftKey && b.push("shift"); a.altKey && b.push("alt"); a.ctrlKey && b.push("ctrl"); a.metaKey && b.push("meta"); return b } function x(a, b, d, c) {
24 | m.stopCallback(b, b.target || b.srcElement, d, c) || !1 !== a(b, d) || (b.preventDefault ? b.preventDefault() : b.returnValue = !1, b.stopPropagation ?
25 | b.stopPropagation() : b.cancelBubble = !0)
26 | } function y(a) { "number" !== typeof a.which && (a.which = a.keyCode); var b = A(a); b && ("keyup" == a.type && z === b ? z = !1 : m.handleKey(b, K(a), a)) } function w(a) { return "shift" == a || "ctrl" == a || "alt" == a || "meta" == a } function L(a, b, d, c) { function e(b) { return function () { u = b; ++n[a]; clearTimeout(D); D = setTimeout(t, 1E3) } } function v(b) { x(d, b, a); "keyup" !== c && (z = A(b)); setTimeout(t, 10) } for (var g = n[a] = 0; g < b.length; ++g) { var f = g + 1 === b.length ? v : e(c || E(b[g + 1]).action); F(b[g], f, c, a, g) } } function E(a, b) {
27 | var d,
28 | c, e, f = []; d = "+" === a ? ["+"] : a.split("+"); for (e = 0; e < d.length; ++e) c = d[e], G[c] && (c = G[c]), b && "keypress" != b && H[c] && (c = H[c], f.push("shift")), w(c) && f.push(c); d = c; e = b; if (!e) { if (!p) { p = {}; for (var g in h) 95 < g && 112 > g || h.hasOwnProperty(g) && (p[h[g]] = g) } e = p[d] ? "keydown" : "keypress" } "keypress" == e && f.length && (e = "keydown"); return { key: c, modifiers: f, action: e }
29 | } function F(a, b, d, c, e) {
30 | q[a + ":" + d] = b; a = a.replace(/\s+/g, " "); var f = a.split(" "); 1 < f.length ? L(a, f, b, d) : (d = E(a, d), l[d.key] = l[d.key] || [], C(d.key, d.modifiers, { type: d.action },
31 | c, a, e), l[d.key][c ? "unshift" : "push"]({ callback: b, modifiers: d.modifiers, action: d.action, seq: c, level: e, combo: a }))
32 | } var h = { 8: "backspace", 9: "tab", 13: "enter", 16: "shift", 17: "ctrl", 18: "alt", 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", 37: "left", 38: "up", 39: "right", 40: "down", 45: "ins", 46: "del", 91: "meta", 93: "meta", 224: "meta" }, B = { 106: "*", 107: "+", 109: "-", 110: ".", 111: "/", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'" }, H = {
33 | "~": "`", "!": "1",
34 | "@": "2", "#": "3", $: "4", "%": "5", "^": "6", "&": "7", "*": "8", "(": "9", ")": "0", _: "-", "+": "=", ":": ";", '"': "'", "<": ",", ">": ".", "?": "/", "|": "\\"
35 | }, G = { option: "alt", command: "meta", "return": "enter", escape: "esc", mod: /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? "meta" : "ctrl" }, p, l = {}, q = {}, n = {}, D, z = !1, I = !1, u = !1; for (f = 1; 20 > f; ++f) h[111 + f] = "f" + f; for (f = 0; 9 >= f; ++f) h[f + 96] = f; s(r, "keypress", y); s(r, "keydown", y); s(r, "keyup", y); var m = {
36 | bind: function (a, b, d) { a = a instanceof Array ? a : [a]; for (var c = 0; c < a.length; ++c) F(a[c], b, d); return this },
37 | unbind: function (a, b) { return m.bind(a, function () { }, b) }, trigger: function (a, b) { if (q[a + ":" + b]) q[a + ":" + b]({}, a); return this }, reset: function () { l = {}; q = {}; return this }, stopCallback: function (a, b) { return -1 < (" " + b.className + " ").indexOf(" mousetrap ") ? !1 : "INPUT" == b.tagName || "SELECT" == b.tagName || "TEXTAREA" == b.tagName || b.isContentEditable }, handleKey: function (a, b, d) {
38 | var c = C(a, b, d), e; b = {}; var f = 0, g = !1; for (e = 0; e < c.length; ++e) c[e].seq && (f = Math.max(f, c[e].level)); for (e = 0; e < c.length; ++e) c[e].seq ? c[e].level == f && (g = !0,
39 | b[c[e].seq] = 1, x(c[e].callback, d, c[e].combo, c[e].seq)) : g || x(c[e].callback, d, c[e].combo); c = "keypress" == d.type && I; d.type != u || w(a) || c || t(b); I = g && "keydown" == d.type
40 | }
41 | }; J.Mousetrap = m; "function" === typeof define && define.amd && define(m)
42 | })(window, document);
43 |
44 |
45 | ////////////////////////////////////////////////////////////////////////////////////////////////////
46 | // BIND KEY COMBOS
47 | ////////////////////////////////////////////////////////////////////////////////////////////////////
48 |
49 | // Pulled from http://www.codinghorror.com/blog/2006/02/standard-browser-keyboard-shortcuts.html
50 |
51 | // Home
52 | Mousetrap.bind("alt+home", function () { quakelive.Goto("home"); });
53 |
54 | // Back
55 | Mousetrap.bind(["alt+left", "backspace"], function () { history.back(); });
56 |
57 | // Forward
58 | Mousetrap.bind(["alt+right", "shift+backspace"], function () { history.forward(); });
59 |
60 | // Refresh
61 | Mousetrap.bind("f5", function () { location.reload(); });
62 |
63 | // Refresh with cache purging (TODO: test if it works...)
64 | Mousetrap.bind("mod+f5", function () { location.reload(true); });
65 |
66 | // URL navigation
67 | Mousetrap.bind("mod+l", function () {
68 | function escapeHandler(e) { if (27 === e.keyCode) cleanup(); }
69 | function cleanup() {
70 | $("#modal-input > input")[0].blur();
71 | $("#navprompt").jqmHide();
72 | $("body").off("keyup", escapeHandler);
73 | }
74 |
75 | qlPrompt({
76 | id: "navprompt"
77 | , title: "Navigate"
78 | , body: "
Warning: A non-QUAKE LIVE URL will open your default browser."
79 | , showX: true
80 | , input: true
81 | , inputLabel: location.href
82 | , ok: function () {
83 | var url = $("#modal-input > input").val().trim();
84 | // If a QL URL, just navigate directly...
85 | if (/^https?:\/\/\w+\.quakelive\.com/.test(url)) {
86 | location.href = url;
87 | }
88 | // ... otherwise trigger opening in a new window
89 | else {
90 | if (0 !== url.indexOf("http:") && 0 !== url.indexOf("https:")) url = "http://" + url;
91 | window.open(url, "_blank");
92 | }
93 | cleanup();
94 | }
95 | , cancel: cleanup
96 | });
97 |
98 | setTimeout(function () {
99 | $("#modal-input > input")[0].select();
100 | $("body").on("keyup", escapeHandler);
101 | }, 0);
102 | });
103 |
--------------------------------------------------------------------------------
/source/scripts/attic/linkify.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id 187258
3 | // @name Linkify
4 | // @version 1.6
5 | // @author PredatH0r
6 | // @description Turn plain text URLs into links
7 | // @unwrap
8 | // ==/UserScript==
9 |
10 | /*******************************************************************************
11 |
12 | Linkify Plus modified by PredatH0r, based on modified version from kry.
13 | This version is tailored to work with Quake Live standalone client and QLHM.
14 |
15 | Version 1.6
16 | - fixed workaround to open internal QL URLs in QL browser insteam of system browser
17 |
18 | Version 1.5
19 | - added workaround to open external links in QL Steam build
20 |
21 | Version 1.4
22 | - fixed cannot call getElementById of undefined
23 |
24 | Version 1.3
25 | - compatibility with redesigned QL UI
26 | - removed code for color selection
27 |
28 | Version 1.2
29 | - fixed link detection in chat after receiving a couple messages
30 |
31 | Version 1.1
32 | - now also handles links in chat messages from non-active chats
33 |
34 | Version 1.0
35 | - fixed drop down list for color selection
36 | - replaced DOM node event listeners with Quake Live specific handlers
37 |
38 |
39 | Linkifier Plus modified by kry. Licensed for unlimited modification
40 | and redistribution as long as this notice is kept intact.
41 |
42 | Only modified Linkify Plus to change the link color to red because
43 | in Quake Live the links appeared in white with white background.
44 | Everything else is written by Anthony Liuallen of http://arantius.com/
45 |
46 | Also I have made minor bugfixes to make it work better in Quake Live.
47 |
48 | Link to Linkify Plus
49 | http://userscripts.org/scripts/show/1352
50 |
51 | ** Linkify plus comments below
52 | Loosely based on the Linkify script located at:
53 | http://downloads.mozdev.org/greasemonkey/linkify.user.js
54 |
55 | Originally written by Anthony Lieuallen of http://arantius.com/
56 | Licensed for unlimited modification and redistribution as long as
57 | this notice is kept intact.
58 |
59 | If possible, please contact me regarding new features, bugfixes
60 | or changes that I could integrate into the existing code instead of
61 | creating a different script. Thank you
62 |
63 | Linkify Plus Red Links Version history:
64 | Version 1.1.9
65 | - Replaced /r/ from the links with /#! - if someone gave you a join link or a link to game stats before it could have reloaded QL again but this prevents it
66 | Version 1.1.8
67 | - Compatibility fix - had UI problems with custom map launcher
68 | Version 1.1.7
69 | - Fixed last version and other small fixes
70 | Version 1.1.6
71 | - Changed where links are opened. All Quake Live links that are not to forums are opened in the same window and the rest will open in a new tab or window.
72 | Version 1.1.5
73 | - Rewrote functions that gave the text to linkifycontainer, now the script should be a lot lighter
74 | Version 1.1.4
75 | - Another bugfix and stopped looking for email addresses.
76 | Version 1.1.3
77 | - Bugfix.
78 | Version 1.1.2
79 | - Removed commands from Greasemonkey "User Script Commands..." menu and added a dropdown selection to change color for better compatibility.
80 | Version 1.1.1
81 | - Still better lagfix.
82 | Version 1.1.0
83 | - Even better lagfix.
84 | Version 1.0.9
85 | - Attemp to fix lag.
86 | Version 1.0.8
87 | - Little improvement to lagfix.
88 | Version 1.0.7
89 | - Improved lagfix.
90 | Version 1.0.6
91 | - Lagfix.
92 | Version 1.0.5
93 | - Made the color changing work better.
94 | Version 1.0.4
95 | - Possibility to change color through Greasemonkey "User Script Commands..." menu.
96 | Version 1.0.3
97 | - Limited the use to Quake Live.
98 | Version 1.0.2
99 | - Changed event listener type. Sometimes a node was not inserted, but modified.
100 | Version 1.0.1
101 | - Parenthesis fix.
102 | Version 1.0.0
103 | - Changed link color to red.
104 |
105 | Using the Linkify Plus version 2.0.2. as a base.
106 |
107 | *******************************************************************************/
108 |
109 | (function() {
110 | var document = window.document;
111 | var quakelive = window.quakelive;
112 |
113 | var notInTags = [ 'a', 'head', 'noscript', 'option', 'script', 'style', 'title', 'textarea' ];
114 | var textNodeXpath = ".//text()[not(ancestor::" + notInTags.join(') and not(ancestor::') + ")]";
115 | var urlRE = /((?:https?|ftp):\/\/[^\s'"'<>]+|www\.[^\s'"'<>]+)/gi;
116 |
117 | var linkcolor = "#CA3827";
118 |
119 | /******************************************************************************/
120 | var oldSelectContact;
121 | var inOnMessage = false;
122 |
123 | function init() {
124 | quakelive.AddOnceHook("OnLayoutLoaded", onLayoutLoaded);
125 | }
126 |
127 | function onLayoutLoaded() {
128 | // handle incoming chat messages
129 | quakelive.AddHook('IM_OnMessage', IM_OnMessage);
130 | oldSelectContact = quakelive.mod_friends.roster.SelectContact;
131 | quakelive.mod_friends.roster.SelectContact = SelectContact;
132 |
133 | // handle content changes
134 | quakelive.AddHook("OnContentLoaded", replaceLinks);
135 | }
136 |
137 | /******************************************************************************/
138 |
139 | function SelectContact(contact) {
140 | try {
141 | oldSelectContact.call(quakelive.mod_friends.roster, contact);
142 | var node = document.getElementById("im-chat-body");
143 | linkifyContainer(node);
144 | } catch (ex) {}
145 | }
146 |
147 | function IM_OnMessage() {
148 | try {
149 | if (inOnMessage)
150 | return;
151 | inOnMessage = true;
152 |
153 | // need to delay this action a bit because the message has not yet been added to the chat window
154 | window.setTimeout(function() {
155 | var node = document.getElementById("im-chat-body");
156 | if (node)
157 | linkifyContainer(node);
158 | }, 300);
159 | }
160 | catch(e) {
161 | }
162 | finally {
163 | inOnMessage = false;
164 | }
165 | }
166 |
167 | function replaceLinks() {
168 | if (document.getElementById("qlv_profileTopLeft"))
169 | linkifyContainer(document.getElementById("qlv_profileTopLeft"));
170 | }
171 |
172 | function linkifyContainer(container) {
173 | var xpathResult = document.evaluate(
174 | textNodeXpath, container, null,
175 | XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null
176 | );
177 |
178 | var i = 0;
179 |
180 | function continuation() {
181 | var node, limit = 0;
182 | while (node = xpathResult.snapshotItem(i++)) {
183 | linkifyTextNode(node);
184 |
185 | if (++limit > 50) {
186 | return setTimeout(continuation, 0);
187 | }
188 | }
189 | }
190 |
191 | setTimeout(continuation, 0);
192 | }
193 |
194 | function linkifyTextNode(node) {
195 | var l, m;
196 | var txt = node.textContent;
197 | var span = null;
198 | var p = 0;
199 |
200 | while (m = urlRE.exec(txt)) {
201 |
202 | if (null == span) {
203 | //create a span to hold the new text with links in it
204 | span = document.createElement('span');
205 | }
206 |
207 | //get the link without trailing dots
208 | l = m[0].replace(/\.*$/, '');
209 | l = l.replace('/r/', '/#!');
210 | //put in text up to the link
211 | span.appendChild(document.createTextNode(txt.substring(p, m.index)));
212 | //create a link and put it in the span
213 | a = document.createElement('a');
214 | a.className = 'linkifyplus';
215 | a.appendChild(document.createTextNode(l));
216 | if (l.match(/^www/i)) {
217 | l = 'http://' + l;
218 | } else if (-1 == l.indexOf('://')) {
219 | l = 'mailto:' + l;
220 | }
221 | if (!l.match("\\.quakelive\\.com/#"))
222 | l = "javascript:quakelive.OpenURL(\"" + l + "\")";
223 | a.setAttribute('href', l);
224 | a.style.color = linkcolor;
225 | if (l.match(/^http:\/\/www\.quakelive\.com\/forum/i) || !(l.match(/^http:\/\/www\.quakelive\.com/i) || l.match(/^https:\/\/secure\.quakelive\.com/i))) {
226 | a.setAttribute('target', '_blank');
227 | }
228 | span.appendChild(a);
229 | //track insertion point
230 | p = m.index + m[0].length;
231 | }
232 | if (span) {
233 | //take the text after the last link
234 | span.appendChild(document.createTextNode(txt.substring(p, txt.length)));
235 | //replace the original text with the new span
236 | try {
237 | if (node && node.parentNode)
238 | node.parentNode.replaceChild(span, node);
239 | } catch(e) {
240 | console.error(e);
241 | console.log(node);
242 | }
243 | }
244 | }
245 |
246 | try {
247 | init();
248 | } catch (e) {
249 | console.log(e);
250 | }
251 | })();
--------------------------------------------------------------------------------
/source/scripts/attic/links.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id links
3 | // @name Links to QL related Websites
4 | // @version 6
5 | // @author PredatH0r
6 | // @description Adds a "Links" menu with links to important QL related websites
7 | // @unwrap
8 | // ==/UserScript==
9 |
10 | /*
11 |
12 | Version 6
13 | - replaced link for "Duel Spawn Logic"
14 |
15 | Version 5.0
16 | - added http://qlmm.gameboni.com/
17 |
18 | Version 4.0
19 | - added http://qlhud.net/
20 |
21 | Version 3.0
22 | - added some links
23 |
24 | Version 2.0
25 | - added some links
26 | - added separator lines to group links
27 |
28 | Version 1.0
29 | - first public release
30 |
31 | */
32 |
33 | (function () {
34 | var menuCaption = "Links";
35 |
36 | extraQL.addStyle("ul.sf-menu li ul li.sep { border-top: 1px solid #888; }");
37 |
38 | function init() {
39 | nav.navbar[menuCaption] = {
40 | id: "eql_links",
41 | callback: "",
42 | submenu: {
43 | "Quakenet IRChat": { newwindow: "http://webchat.quakenet.org/?nick=" + quakelive.username + "&channels=quakelive&prompt=1" },
44 | "ESReality Forum": { newwindow: "http://www.esreality.com/?a=post&forum=17" },
45 | "Reddit Forum": { newwindow: "http://www.reddit.com/r/quakelive"},
46 | "Quakehub Videos": { newwindow: "http://quakehub.com/" },
47 | "Quake History": { newwindow: "http://www.quakehistory.com/" },
48 | "QLRanks": { newwindow: "http://www.qlranks.com/", "class": "sep" },
49 | "QLStats": { newwindow: "http://ql.leeto.fi/#/" },
50 | "Duel Match Maker": { newwindow: "http://qlmm.gameboni.com/" },
51 | "FaceIt Cups": { newwindow: "http://play.faceit.com/" },
52 | "125fps League": { newwindow: "https://twitter.com/125fps" },
53 | "vHUD Editor": { newwindow: "http://visualhud.pk69.com/", "class": "sep" },
54 | "qlhud Custom HUDs": { newwindow: "http://qlhud.net/"},
55 | "Wolfcam/Whisperer": { newwindow: "http://www.wolfwhisperer.net/"},
56 | "Yakumo's QL Guide": { newwindow: "http://www.quakelive.com/forum/showthread.php?831-The-Ultimate-Quake-Live-Guide", "class": "sep" },
57 | "QL Console Guide": { newwindow: "http://www.regurge.at/ql/" },
58 | "Strafing Theory": { newwindow: "http://www.funender.com/quake/articles/strafing_theory.html" },
59 | "Duel Spawn Logic": { newwindow: "http://www.esreality.com/wiki/Quake_Live:_Strategy:_Duel:_Spawn_Control" }
60 | }
61 | };
62 | }
63 |
64 | init();
65 |
66 | })(window);
--------------------------------------------------------------------------------
/source/scripts/attic/matchtip.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id 186527
3 | // @name Quake Live Left-Hand-Side Playerlist Popup
4 | // @version 1.6
5 | // @description Displays a QuakeLive server's player list on the left side of the cursor
6 | // @author PredatH0r
7 | // @include http://*.quakelive.com/*
8 | // @exclude http://*.quakelive.com/forum*
9 | // @unwrap
10 | // ==/UserScript==
11 |
12 | /*
13 |
14 | Version 1.6
15 | - fixed interference with server browser details no longer scrolling
16 | - code cleanup
17 |
18 | Version 1.5
19 | - compatibility with "join URL" and fixed location "chat"
20 |
21 | Version 1.4
22 | - fixed clipping of tool tip with low y-coord and flicker when scrolling window
23 |
24 | Version 1.3
25 | - moved popup up so that current server browser like is fully visible
26 |
27 | Version 1.2:
28 | - added try/catch
29 | - fixed timing gap between read/write of DisplayMatchPlayers
30 |
31 | */
32 |
33 | (function (win) { // limit scope of variables and functions
34 |
35 | var quakelive = win.quakelive;
36 | var oldShowTooltip;
37 | var oldDisplayMatchPlayers;
38 |
39 | function init() {
40 | oldShowTooltip = quakelive.matchtip.ShowTooltip;
41 | oldDisplayMatchPlayers = quakelive.matchtip.DisplayMatchPlayers;
42 |
43 | quakelive.matchtip.ShowTooltip = function (tip, node, content, footer, showArrow) {
44 | this.reposnode = node;
45 | this.repostip = tip;
46 | this.reposshowArrow = showArrow;
47 |
48 | this.reposForChat = node.parents(".rosteritem").length > 0;
49 |
50 | var ret = oldShowTooltip.call(this, tip, node, content, footer, showArrow);
51 | this.RepositionTip();
52 | return ret;
53 | }
54 |
55 | quakelive.matchtip.RepositionTip = function () {
56 | try {
57 | var ARROW_HEIGHT = 175;
58 | var ctx = quakelive.matchtip;
59 | var node = ctx.reposnode;
60 | var tip = ctx.repostip;
61 | var showArrow = ctx.reposshowArrow;
62 |
63 | var nodeCenter = node.offset().top + node.innerHeight() / 2;
64 | var deltaY = $(document).scrollTop();
65 |
66 | var top, left, $arrow;
67 | if (ctx.reposForChat) {
68 | // position the tip window in chat area on left-hand side of the node
69 | left = node.offset().left;
70 | top = nodeCenter - tip.outerHeight() / 2 - deltaY;
71 | if (top < 0) top = 0;
72 | tip.css({ "position": "fixed", "left": (left - 21 - tip.outerWidth()) + "px", "top": top + "px" });
73 |
74 | // position the arrow
75 | $("#lgi_arrow_left").remove();
76 | $arrow = $("#lgi_arrow_right");
77 | if (showArrow) {
78 | if ($arrow.length == 0) {
79 | $arrow = $("
");
80 | $("#lgi_srv_fill").append($arrow);
81 | }
82 | top = nodeCenter - ARROW_HEIGHT / 2 - deltaY;
83 | $arrow.css({ "position": "fixed", "left": (left - 25) + "px", "top": top + "px" });
84 | }
85 | }
86 | else {
87 | // position the tip window in content area on right-hand side of the node
88 | left = node.offset().left + node.outerWidth() + 22;
89 | top = nodeCenter - 309 / 2 - deltaY;
90 | if (top < 0) top = 0;
91 | tip.css({ "position": "absolute", "left": left + "px", "top": top + "px" });
92 |
93 | // position the arrow
94 | $("#lgi_arrow_right").remove();
95 | if (showArrow) {
96 | $arrow = $("#lgi_arrow_left");
97 | if ($arrow.length == 0) {
98 | $arrow = $("
");
99 | $("#lgi_srv_fill").append($arrow);
100 | }
101 | top = nodeCenter - ARROW_HEIGHT / 2 - deltaY - top;
102 | $arrow.css({ "position": "absolute", "left": "-21px", "top": top + "px" });
103 | }
104 | }
105 | } catch (e) {
106 | console.log("^1" + e.stack + "^7");
107 | }
108 | }
109 |
110 | quakelive.matchtip.DisplayMatchPlayers = function (server) {
111 | var ret = oldDisplayMatchPlayers.call(this, server);
112 | try {
113 | // add a close button to the player list window
114 | var closeBtn = $("#lgi_headcol_close");
115 | if (closeBtn.length == 0)
116 | $("#lgi_cli_top").append('
');
117 |
118 | // position player list window
119 | var $tip = $('#lgi_tip');
120 | var top = parseInt($tip.css("top")) + 3;
121 | var $cli = $("#lgi_cli");
122 | if (this.reposForChat)
123 | $cli.css({ "position": "fixed", "left": ($tip.offset().left - $cli.outerWidth() + 4) + "px", "top": top + "px", "z-index": 22000 });
124 | else {
125 | var left = $tip.offset().left + $tip.outerWidth();
126 | $cli.css({ "position": "absolute", "left": left + "px", "right": "auto", "top": top + "px", "z-index": 22000 });
127 | }
128 | }
129 | catch(e) {}
130 | return ret;
131 | };
132 | }
133 |
134 | init();
135 | })(window);
--------------------------------------------------------------------------------
/source/scripts/attic/openChat.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id openChat
3 | // @name Auto-Open chat when starting QL
4 | // @description Opens the chat window when QL starts
5 | // @author PredatH0r
6 | // @version 1.0
7 | // @unwrap
8 | // ==/UserScript==
9 |
10 | (function () {
11 | extraQL.showTabPage("qlv_chatControl");
12 | })();
--------------------------------------------------------------------------------
/source/scripts/attic/playerStatus.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id 187322
3 | // @name Quake Live Player Status
4 | // @version 1.2
5 | // @description Shows match information in your friend list
6 | // @author PredatH0r, rahzei
7 | // @unwrap
8 | // ==/UserScript==
9 |
10 | /*
11 | This script is modified version of rahzei's http://userscripts.org/scripts/review/96328 version 0.4
12 | It is now compatible with the QL standalone launcher and QLHM
13 |
14 | Version 1.2
15 | - fixed "cannot read property game_type of undefined" error message, when there's an invalid game in the friend list
16 |
17 | Version 1.1
18 | - fixed "Uncaught TypeError: Cannot read property 'ADDRESS' of null" error messges
19 |
20 | */
21 |
22 | (function (unsafeWindow) { // enforce a local scope for variables and functions
23 |
24 | var quakelive, $;
25 |
26 | var GameTypes = {
27 | 0: "FFA",
28 | 1: "Duel",
29 | 2: "Race",
30 | 3: "TDM",
31 | 4: "CA",
32 | 5: "CTF"
33 | };
34 |
35 | var Locations = {
36 | 40: 'ARG',
37 | 14: 'AUS',
38 | 33: 'AUS',
39 | 35: 'AUS',
40 | 51: 'AUS',
41 | 26: 'CAN',
42 | 38: 'CHL',
43 | 18: 'DE',
44 | 28: 'ES',
45 | 20: 'FR',
46 | 19: 'UK',
47 | 41: 'ISL',
48 | 42: 'JPN',
49 | 49: 'KOR',
50 | 17: 'NL',
51 | 32: 'PL',
52 | 37: 'RO',
53 | 39: 'RO',
54 | 44: 'RU',
55 | 47: 'SRB',
56 | 29: 'SE',
57 | 58: 'UKR',
58 | 6: 'TX',
59 | 10: 'CA',
60 | 11: 'VA',
61 | 16: 'CA',
62 | 21: 'IL',
63 | 22: 'GA',
64 | 23: 'WA',
65 | 24: 'NY',
66 | 25: 'CA',
67 | 46: 'ZAF'
68 | };
69 |
70 | function GM_addStyle(css) {
71 | $("head").append("");
72 | }
73 |
74 |
75 | /*
76 | Copy from ql source, out of scope
77 | */
78 |
79 | function JID(a) {
80 | this.bare = a;
81 | var d = a.indexOf("@");
82 | if (d != -1) this.username = a.substring(0, d);
83 | else {
84 | this.username = a;
85 | this.bare = a + "@" + quakelive.siteConfig.xmppDomain
86 | }
87 | }
88 |
89 | JID.prototype.Clone = function () {
90 | return new JID(this.bare)
91 | };
92 |
93 | /*
94 | Wait for playerlist to make it into the DOM
95 | */
96 |
97 | function init() {
98 | try {
99 | // setup our globals
100 | $ = unsafeWindow.jQuery;
101 | quakelive = unsafeWindow.quakelive;
102 |
103 | // Loop timer till playerlist is complete
104 | if ($(".player_name").length == 0) {
105 | window.setTimeout(init, 1000);
106 | //console.log("Waiting on quakelive object init...");
107 | return;
108 | }
109 |
110 | quakelive.AddHook("IM_OnConnected", init);
111 |
112 | // Override OnPresence()
113 | QuakeLiveChatExtender();
114 |
115 | // Run initial update on player_name nodes
116 | initRoster();
117 | }
118 | catch (e) { }
119 | }
120 |
121 | function QuakeLiveChatExtender() {
122 |
123 | // Override
124 | quakelive.xmppClient.roster.OnPresence = function (a) {
125 | try {
126 | if (a = quakelive.Eval(a)) {
127 | var d = new JID(a.who);
128 | if (typeof this.items[d.username] != "undefined") {
129 | d = this.items[d.username];
130 | this.ChangeItemPresence(d, a.presence);
131 | d.status = a.status;
132 | var nick = a.who.substring(0, a.who.indexOf("@"));
133 |
134 | // update status message
135 | updateNode(nick, a.status);
136 |
137 | }
138 | }
139 | }
140 | catch (e) { }
141 | };
142 | }
143 |
144 | /*
145 | Updates the rosteritem with the correct nick
146 | */
147 |
148 | function updateNode(nick, status) {
149 | try {
150 | status = quakelive.Eval(status);
151 | $(".player_name").each(function (i, item) {
152 | var that = this;
153 | var prehtml = $(this).html();
154 | var nodeNick = $(this).html().indexOf("
") >= 0 ? $(this).html().substring(prehtml.indexOf("") + "".length).toLowerCase() : $(this).html().toLowerCase();
155 |
156 | // FIXED: I'm an idiot & id changes stuff all the time :(
157 | nodeNick = nodeNick.substring(nodeNick.indexOf("
") + "".length, nodeNick.indexOf(""));
158 | nodeNick = nodeNick.indexOf("
") >= 0 ? nodeNick.substring(0, nodeNick.indexOf("
")) : nodeNick;
159 |
160 | if (nodeNick != nick)
161 | return;
162 |
163 | // Idle
164 | if (!status) {
165 | // cut off status
166 | $(that).html(prehtml.substring(prehtml.indexOf("
")));
167 |
168 | var rosteritem = $(that).parent().parent();
169 |
170 | $(rosteritem).children(".rosteritem-name").css({ 'margin-bottom': '0px' });
171 |
172 | if ($(rosteritem).hasClass('rosteritem-selected'))
173 | $(rosteritem).css({ 'height': '26px' });
174 | else
175 | $(rosteritem).css({ 'height': '20px' });
176 |
177 | return;
178 | }
179 |
180 |
181 | // Online game
182 | if (status.ADDRESS.length > 8) {
183 | $.ajax({
184 | url: "/browser/details",
185 | data: {
186 | ids: status.SERVER_ID
187 | },
188 | dataType: "json",
189 | port: "joinserver",
190 | cache: false,
191 | success: function (x) {
192 | if (($.isArray(x) || x.length > 0) && x[0]) {
193 | var gametype = (typeof GameTypes[x[0].game_type] != 'undefined') ? GameTypes[x[0].game_type] : x[0].game_type_title;
194 |
195 | if (prehtml.indexOf("
0)
196 | prehtml = prehtml.substring(0, prehtml.indexOf("
"));
197 |
198 | var str = prehtml + "
";
199 |
200 | // teamgame
201 | if (x[0].teamsize) {
202 | str += "" + gametype + " on ";
203 |
204 | if (x[0].g_gamestate === "PRE_GAME" && x[0].num_players < (x[0].teamsize * 2))
205 | str += x[0].map_title + " - " + Locations[x[0].location_id] + " (" + x[0].num_players + "/" + (x[0].teamsize * 2) + ")
";
206 | else
207 | str += x[0].map_title + " - " + Locations[x[0].location_id] + " (" + x[0].num_players + "/" + (x[0].teamsize * 2) + ") ";
208 | }
209 |
210 | // duel
211 | else
212 | str += "" + gametype + " on " + x[0].map_title + " - " + Locations[x[0].location_id] + " (" + x[0].num_players + "/" + x[0].max_clients + ")";
213 |
214 | $(that).html(str);
215 |
216 | $(that).parent().parent().css({ 'height': '35px' });
217 | }
218 | }
219 | });
220 |
221 | // Prac game
222 | } else if (status.ADDRESS === "loopback") {
223 | if (prehtml.indexOf("
0)
224 | prehtml = prehtml.substring(0, prehtml.indexOf("
"));
225 |
226 | $(that).html(prehtml + "
Playing Practice Game
");
227 |
228 | $(that).parent().parent().css({ 'height': '35px' });
229 |
230 |
231 | // Demo
232 | } else if (status.ADDRESS === "bot") {
233 | if (prehtml.indexOf("
0)
234 | prehtml = prehtml.substring(0, prehtml.indexOf("
"));
235 |
236 | $(that).html(prehtml + "
Watching demo
");
237 |
238 | $(that).parent().parent().css({ 'height': '35px' });
239 | }
240 | });
241 | }
242 | catch (e) { }
243 | }
244 |
245 | function updateRoster() {
246 | try {
247 | if (unsafeWindow.fullScreen)
248 | return;
249 |
250 | var items = unsafeWindow.quakelive.xmppClient.roster.items;
251 |
252 | for (var i in items) {
253 | if (items[i].status && typeof items[i].status != "undefined") {
254 | updateNode(items[i].jid.username, items[i].status);
255 | }
256 | }
257 | }
258 | catch (e) { }
259 | }
260 |
261 | /*
262 | Iterates over roster.items from the xmppClient object and updates status where due
263 | */
264 |
265 | function initRoster() {
266 | $ = unsafeWindow.jQuery;
267 |
268 | // Update roster, so status is the correct info regarding players/maxplayers etc
269 | window.setInterval(updateRoster, 120 * 1000);
270 |
271 | GM_addStyle(".rosteritem-selected { text-align:left;font-weight:bold;font-size:12px;height:26px;line-height:20px;border-bottom:1px solid #a1a0a3;background:url() repeat-y #fff !important }");
272 |
273 | // resize chat area
274 | window.setTimeout(function () {
275 | updateRoster();
276 | //$("#im-body").css({'height':'500px'});
277 | GM_addStyle(".rosteritem-selected { text-align:left;font-size:12px;height:26px;line-height:20px;border-bottom:1px solid #a1a0a3;background:url() repeat-y #fff !important }");
278 | GM_addStyle(".rosteritem-selected .rosteritem-name { font-weight:bold;line-height:20px; !important }");
279 |
280 | }, 10 * 1000);
281 | }
282 |
283 | init();
284 | })(window);
285 |
--------------------------------------------------------------------------------
/source/scripts/attic/profileJumper.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id 113249
3 | // @name Quake Live Better Profile Jumper
4 | // @version 1.4
5 | // @namespace phob.net
6 | // @author wn
7 | // @description Keeps your current Profile tab when using "Jump to profile..."
8 | // @run-at document-end
9 | // ==/UserScript==
10 |
11 |
12 | if (/offline/i.test(document.title) || window.self != window.top) {
13 | return;
14 | }
15 |
16 | quakelive.mod_profile.ProfileJumpClick = function() {
17 | var player_name = $("#profile_jump_input").val();
18 | if ("undefined" == typeof player_name) return;
19 |
20 | player_name = $.trim(player_name);
21 | if (!player_name.length) return;
22 |
23 | var parts = [quakelive.pathParts[0], quakelive.pathParts[1], player_name];
24 | if (/^\d{4}(?:-\d{2}){2}$/.test(quakelive.pathParts[3])) parts.push(quakelive.pathParts[3]);
25 |
26 | quakelive.Goto(parts.join("/"));
27 | return false;
28 | }
29 |
--------------------------------------------------------------------------------
/source/scripts/attic/raceTop10.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id 424643
3 | // @name Quake Live Race Leaders
4 | // @description Adds a "top10" command to the QL console to show the top 10 race scores
5 | // @version 1.2
6 | // @author PredatH0r
7 | // @include http://*.quakelive.com/*
8 | // @exclude http://*.quakelive.com/forum*
9 | // @unwrap
10 | // ==/UserScript==
11 |
12 | /*
13 |
14 | Use "top10 ." to get the top scores for the current map.
15 | Use "top10 basesiege" to get the top scores for map "basesiege".
16 | You can bind this script to a key (e.g. "P") by using: /bind p "top10 ."
17 |
18 | The output mode can be configured by setting _race_outputMethod to either echo/print/say_team/say
19 | If no _race_outputMethod is set, the _qlrd_outputMethod is used, which can be toggled by the QLrank.com Display script.
20 | If neither is set, "echo" will be used.
21 |
22 | Version 1.2
23 | - fixed LaunchGame, which caused incompatibilities with other scripts (e.g. ingame-friend-commands)
24 |
25 | Version 1.1
26 | - removed clan tags from names
27 |
28 | Version 1.0
29 | - first version working with QLHM
30 |
31 | */
32 |
33 | (function () {
34 | // external global variables
35 | var quakelive = window.quakelive;
36 | var qz_instance = window.qz_instance;
37 | var console = window.console;
38 |
39 | // constants
40 | var COMMAND = "top10";
41 | var INFO = '"Use \'^5' + COMMAND + ' .^7\' or \'^5' + COMMAND + ' ^7<^5mapname^7>\' to show top 10 race scores"';
42 | var CVAR_RACE_OUTPUTMETHOD = "_race_outputMethod";
43 | var CVAR_QLRD_OUTPUTMETHOD = "_qlrd_outputMethod";
44 |
45 | // local variables
46 | var oldOnCvarChanged;
47 | var oldLaunchGame;
48 | var activeServerReq = false;
49 |
50 | function log(msg) {
51 | if (quakelive.IsGameRunning())
52 | qz_instance.SendGameCommand("echo \"" + msg + "\"");
53 | else
54 | console.log(msg);
55 | };
56 |
57 | function init() {
58 | if (!oldOnCvarChanged) {
59 | oldOnCvarChanged = window.OnCvarChanged;
60 | oldLaunchGame = window.LaunchGame;
61 | }
62 | window.LaunchGame = launchGame;
63 | window.OnCvarChanged = onCvarChanged;
64 | }
65 |
66 | function launchGame(params, server) {
67 | params.Append('+set ' + COMMAND + ' "^7"');
68 | params.Append('+set ' + COMMAND + ' ' + INFO);
69 | return oldLaunchGame.call(this, params, server);
70 | }
71 |
72 | function onCvarChanged(name, value, rsync) {
73 | try {
74 | if (name == COMMAND) {
75 | if (value && !activeServerReq) {
76 | activeServerReq = true;
77 | getData(value);
78 | }
79 | quakelive.cvars.Set(COMMAND, INFO);
80 | return;
81 | }
82 | } catch (e) {
83 | log("onCvarChanged:" + e);
84 | activeServerReq = false;
85 | return;
86 | }
87 | oldOnCvarChanged(name, value, rsync);
88 | }
89 |
90 | function getData(mapName) {
91 | try {
92 | if (mapName != "" && mapName != ".") {
93 | requestMapData(mapName);
94 | return;
95 | }
96 |
97 | quakelive.serverManager.RefreshServerDetails(quakelive.currentServerId, {
98 | cacheTime: 0,
99 | onSuccess: onServerDetailsRefreshed,
100 | onError: function() {
101 | log("^1Failed to update server info^7");
102 | activeServerReq = false;
103 | }
104 | });
105 | } catch (e) {
106 | log("getData: " + e);
107 | activeServerReq = false;
108 | }
109 | }
110 |
111 | function onServerDetailsRefreshed() {
112 | try {
113 | var server = quakelive.serverManager.GetServerInfo(quakelive.currentServerId);
114 | var map = server.map;
115 | requestMapData(map);
116 | } catch (e) {
117 | log("onServerDetailsRefreshed: " + e);
118 | activeServerReq = false;
119 | }
120 | }
121 |
122 | function requestMapData(map) {
123 | $.getJSON("/race/map/" + map, function (data) { processData(data, map); });
124 | }
125 |
126 | function processData(data, map) {
127 | try {
128 | var method = quakelive.cvars.Get(CVAR_RACE_OUTPUTMETHOD).value;
129 | if (!method)
130 | method = quakelive.cvars.Get(CVAR_QLRD_OUTPUTMETHOD).value;
131 | if (!method)
132 | method = "echo";
133 |
134 | var i;
135 | var count = data.scores.length;
136 | if (count > 10)
137 | count = 10;
138 | var renderState = { hasPersonal: false, output: [ ], lineLength: 0 };
139 |
140 | for (i = 0; i < count; i++) {
141 | var score = data.scores[i];
142 | renderScore(method, i+1, score, renderState);
143 | }
144 |
145 | if (data.personal && !renderState.hasPersonal) {
146 | renderScore(method, data.personal.rank + 1, data.personal, renderState);
147 | }
148 |
149 | var delay = method == "say" || method == "say_team" ? 1050 : 0;
150 | renderState.output.splice(0, 0, "^7Top 10 race results on ^3" + map + "^7");
151 | printLines(method, delay, renderState.output, 0);
152 | } catch (e) {
153 | log("processData:" + e);
154 | activeServerReq = false;
155 | }
156 | }
157 |
158 | function renderScore(method, rank, score, renderState) {
159 | try {
160 | var isPersonal = false;
161 | var color = "^2";
162 | if (score.name == quakelive.username) {
163 | color = "^1";
164 | isPersonal = true;
165 | }
166 |
167 | //var clan = score.PLAYER_CLAN;
168 | var name = score.name;
169 |
170 | var seconds = Math.floor(score.score / 1000);
171 | var millis = "00" + (score.score % 1000);
172 | millis = millis.substr(millis.length - 3, 3);
173 | var time = seconds + "." + millis;
174 |
175 | rank = rank < 10 ? "0" + rank : "" + rank;
176 |
177 | if (method == "echo") {
178 | var when = window.formatDate(new Date(score.date), 'yyyy-MM-dd HH:mm');
179 | renderState.output.push(color + rank + "^7 " + when + " " + color + time + "^7 " + name);
180 | }
181 | else {
182 | var msg = " " + color + rank + "^7 " + name + " ^5" + time;
183 | if (renderState.output.length > 0 && renderState.output[renderState.output.length - 1].length + msg.length < 90) {
184 | renderState.output[renderState.output.length - 1] += msg;
185 | } else {
186 | renderState.output.push(msg);
187 | }
188 | }
189 | renderState.hasPersonal |= isPersonal;
190 | } catch (e) {
191 | log("renderScore:" + e);
192 | }
193 | }
194 |
195 | function printLines(method, delay, lines, index) {
196 | try {
197 | if (index >= lines.length) {
198 | activeServerReq = false;
199 | return;
200 | }
201 |
202 | qz_instance.SendGameCommand(method + " " + lines[index]);
203 | if (delay <= 0)
204 | printLines(method, delay, lines, index + 1);
205 | else
206 | window.setTimeout(function() { printLines(method, delay, lines, index + 1); }, delay);
207 | } catch (e) {
208 | log("printLines: " + e);
209 | }
210 | }
211 |
212 | init();
213 | })();
--------------------------------------------------------------------------------
/source/scripts/attic/raceboard.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id 465415
3 | // @name Quake Live QLStats Race Leader Boards
4 | // @version 1.1
5 | // @author PredatH0r
6 | // @contributors Rulex
7 | // @description Show separate leader boards for PQL/VQL with/without weapons and option to list all maps for a specific user
8 | // @include http://*.quakelive.com/*
9 | // @exclude http://*.quakelive.com/forum*
10 | // @unwrap
11 | // ==/UserScript==
12 |
13 | /*
14 |
15 | This script modifies the "Community / Race Leaders" page inside the Quake Live UI and adds links to alternative leader boards for
16 | - PQL with weapons
17 | - PQL strafe only
18 | - VQL with weapons
19 | - VQL strafe only
20 | and provides an overview of a player's top-scores on all maps.
21 |
22 | Data is taken from Rulex' QLStats database hosted on http://ql.leeto.fi/
23 | Big thanks to Rulex for providing the data and making the necessary API changes to enable the race leader boards.
24 |
25 | Version 1.1
26 | - added leader and top score to "All maps" view
27 |
28 | Version 1.0
29 | - first public release
30 |
31 | */
32 |
33 | (function () {
34 | var API_URL = "http://ql.leeto.fi/";
35 | //var API_URL = "http://127.0.0.1:8585/";
36 |
37 | var oldShowBoard;
38 | var mode = -2;
39 | var updateBusy = false;
40 |
41 | function init() {
42 | addStyle(
43 | "#raceBoardLinks { color: white; padding: 3px 10px; font-size: 14px; }",
44 | "#raceBoardLinks a { color: white; font-size: 14px; text-decoration: underline; }",
45 | "#raceBoardLinks a.active { color: #FF4422; }",
46 | "#raceBoardLinks input { margin-left: 15px; margin-right: 4px; margin-top: 1px; }",
47 | "#raceBoardUpdate { float: right; margin-right: 10px; }",
48 | "#raceBoardUpdate input[type=text] { height: 22px; width: 150px; padding: 0 4px; }",
49 | "#raceBoardUpdate input[type=button] { height: 22px; width: 100px; margin-left: 5px; cursor: pointer; }",
50 | ".raceBoardYourScore { font-size: 18pt; font-weight: normal; text-align: right; padding-right: 10px; margin-top: 5px; }",
51 | ".raceBoardYourScoreSmall { font-size: 14pt; font-weight: normal; text-align: right; padding-right: 10px; }",
52 | ".raceBoardLeaderScore { font-size: 10pt; font-weight: normal; text-align: right; padding-right: 10px; }"
53 | );
54 |
55 | oldShowBoard = quakelive.mod_race.ShowBoard;
56 | quakelive.mod_race.ShowBoard = showOfficialBoard;
57 | quakelive.AddHook("OnContentLoaded", onContentLoaded);
58 | onContentLoaded();
59 | }
60 |
61 | function addStyle(/*...*/) {
62 | var css = "";
63 | for (var i = 0; i < arguments.length; i++)
64 | css += "\n" + arguments[i];
65 | $("head").append("");
66 | }
67 |
68 | function onContentLoaded() {
69 | var $racedesc = $("#mod_race #header .racedesc");
70 | $racedesc.css("padding-bottom", "7px");
71 | $racedesc.after("");
79 | $("#mapchooser").parent().prepend(
80 | "Force update of QLStats database:" +
81 | "
");
82 | $("#raceBoardLinks a").click(function () {
83 | mode = $(this).data("mode");
84 | updateHighlight();
85 | if (mode == -1)
86 | $("#raceBoardJustMe").attr("checked", false);
87 | showQlstatsBoard(mode);
88 | });
89 | $("#raceBoardLinks input").click(showQlstatsBoard);
90 | $("#raceBoardUpdate input:button").click(updateQlstats);
91 | $("#mod_race .scores").data("fill-height", "350"); // enable auto-sizing through extraQL
92 | }
93 |
94 | function updateHighlight() {
95 | $("#raceBoardLinks a.active").removeClass("active");
96 | if (mode >= -1)
97 | $("#raceBoardLinks a:eq(" + (mode + 1) + ")").addClass("active");
98 | }
99 |
100 | function updateQlstats() {
101 | if (updateBusy)
102 | return;
103 | var player = $("#raceBoardUpdateNickname").val();
104 | if (!player)
105 | return;
106 | updateBusy = true;
107 | $("#raceBoardUpdateButton").val("Updating...").attr("disabled", true);
108 | $.ajax({
109 | url: API_URL + "api/players/" + encodeURIComponent(player) + "/update",
110 | dataType: "jsonp",
111 | success: function () {
112 | $("#raceBoardUpdateButton").val("Update player").attr("disabled", false);
113 | showQlstatsBoard();
114 | },
115 | error: function() {
116 | $("#raceBoardUpdateButton").val("Update failed").attr("disabled", false);
117 | },
118 | complete: function() {
119 | updateBusy = false;
120 | }
121 | });
122 | }
123 |
124 | function showOfficialBoard(map) {
125 | oldShowBoard.call(quakelive.mod_race, map);
126 | mode = -1;
127 | updateHighlight();
128 | $("#raceBoardJustMe").attr("checked", false);
129 | }
130 |
131 | function showQlstatsBoard() {
132 | var justMe = $("#raceBoardJustMe").attr("checked");
133 | if (justMe && mode < 0) {
134 | mode = 0;
135 | updateHighlight();
136 | }
137 | if (mode == -1) {
138 | $("#mapchooser").trigger("change");
139 | return;
140 | }
141 | var map = $("#mapchooser").val();
142 | var url = API_URL;
143 | url += justMe ? "api/race/players/" + quakelive.username : "api/race/maps/" + map;
144 | url += "?ruleset=" + ((mode & 2) ? "vql" : "pql");
145 | url += "&weapons=" + ((mode & 1) ? "off" : "on");
146 | url += "&limit=100";
147 | url += "&player=" + quakelive.username;
148 | $.ajax({
149 | url: url,
150 | dataType: "jsonp",
151 | success: function(data) { fillBoard(data, justMe); },
152 | error: function() { fillBoard(); }
153 | });
154 | }
155 |
156 | function fillBoard(data, justMe) {
157 | var $scores = $("#mod_race .scores");
158 | $scores.empty();
159 |
160 | if (!data || !data.data || !data.data.scores) {
161 | $scores.text("Could not load data");
162 | return;
163 | }
164 |
165 | var playerIndex = -1;
166 | var html = "";
167 | $.each(data.data.scores, function (i, pb) {
168 | html += renderScore(pb, false, justMe);
169 | if (pb.PLAYER_NICK == quakelive.username)
170 | playerIndex = i;
171 | });
172 | $scores.append(html);
173 |
174 | html = "";
175 | var $pb = $("#mod_race .personalbest");
176 | $pb.empty();
177 | if (playerIndex >= 0 && !justMe)
178 | html += renderScore(data.data.scores[playerIndex], true, justMe);
179 | $pb.append(html);
180 | }
181 |
182 | function renderScore(pb, personal, justMe) {
183 | var medal = personal ? " personalscore" : pb.RANK == 1 ? " gold" : pb.RANK == 2 ? " silver" : pb.RANK == 3 ? " bronze" : "";
184 | var html = "";
185 | var descr;
186 | var leaderInfo = "";
187 | var scoreStyle = "raceBoardYourScore";
188 | if (justMe) {
189 | html += "";
190 | descr = pb.MAP;
191 | if (pb.LEADER_SCORE)
192 | leaderInfo = " " + pb.LEADER_NICK + ": " + formatScore(pb.LEADER_SCORE) + "
";
193 | scoreStyle = "raceBoardYourScoreSmall";
194 | } else {
195 | html += "";
196 | descr = pb.PLAYER_NICK;
197 | }
198 | html += "";
199 | html += "
";
200 | html += "
" + pb.RANK + ".
" + descr + " ";
201 | html += "
" + window.formatDate(new Date(pb.GAME_TIMESTAMP), 'yyyy-MM-dd HH:mm') + "
";
202 | html += "
";
203 | html += "
";
204 | html += "
" + formatScore(pb.SCORE) + "
";
205 | html += leaderInfo;
206 | html += "
";
207 | html += "
";
208 | html += "";
209 | return html;
210 | }
211 |
212 | function formatScore(score) {
213 | if (!score) return "";
214 | if (!score.substring) score = score.toString();
215 | return score.substring(0, score.length - 3) + "." + score.slice(-3);
216 | }
217 |
218 | init();
219 | })();
--------------------------------------------------------------------------------
/source/scripts/attic/resizeLayout.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id resizeLayout
3 | // @name Quake Live Layout Resizer
4 | // @version 1.2
5 | // @author PredatH0r
6 | // @description Optimize the QL web UI to take advantage of your actual screen/window size
7 | // @unwrap
8 | // ==/UserScript==
9 |
10 | /*
11 |
12 | This script finds the optimal layout for the QL chat popup, based on the current window size.
13 |
14 | When the window is less then 1310 pixels wide, the chat will overlap the content area,
15 | but leaves some space on the top (configurable by web_chatOverlapIndent, default 150)
16 | so you can access the navigation menus and "Customize" in the server browser.
17 |
18 | If the window is wider, the chat will be shown full-height outside the content area.
19 |
20 | Version 1.2
21 | - removed re-appearing "send" button in chat window (again)
22 |
23 | Version 1.1
24 | - removed re-appearing "send" button in chat window
25 |
26 | Version 1.0
27 | - twitter iframe is now also resized to use full screen height
28 | - fixed in-game chat/friend list
29 |
30 | Version 0.9
31 | - chat is now correctly resizing
32 | - server browser detail window is now correctly resizing
33 | - centered pages are pushed to the left if they would be hidden by the chat window
34 |
35 | Version 0.8
36 | - restored most of the original functions that got lost due to cross-domain CSS loading
37 |
38 | Version 0.7
39 | - hotfix to prevent endless-loop when loading QL
40 |
41 | Version 0.6
42 | - updated extraQL script url to sourceforge
43 |
44 | Version 0.5
45 | - fixed z-index to work with standard content, chat window, drop-down menus and dialog boxes
46 |
47 | Version 0.4
48 | - switched back to horizontal "Chat" bar, always located on the bottom edge
49 | - introduced cvar web_chatOverlapIndent to customize how much space should be left on
50 | top, when the chat is overlapping the main content area
51 | - adjusts z-index of chat and navigation menu to prevent undesired overlapping
52 |
53 |
54 | CVARS:
55 | - web_chatOverlapIndent: number of pixels to skip from top-edge that won't be overlapped
56 | by an expanded chat window
57 |
58 | */
59 |
60 | (function () {
61 | // external variables
62 | var quakelive = window.quakelive;
63 | var extraQL = window.extraQL;
64 |
65 | // constants
66 | var RIGHT_MARGIN = 0;
67 | var CVAR_chatOverlapIndent = "web_chatOverlapIndent";
68 |
69 | // variables
70 | var oldOnResize;
71 | var oldOnCvarChanged;
72 | var oldSelectContact;
73 | var oldRenderMatchDetails;
74 | var styleFullHeight;
75 |
76 |
77 | function init() {
78 | extraQL.addStyle(".chatBox { " +
79 | "border-left: 3px solid #444; " +
80 | "border-top: 3px solid #444; " +
81 | "border-right: 3px solid #444; " +
82 | "position: fixed; " +
83 | "bottom: 27px; " +
84 | "right: 0px; " +
85 | "}");
86 | extraQL.addStyle(
87 | "#chatContainer.expanded #collapsableChat { background-color: rgb(114,24,8); }",
88 | "#chatContainer .fullHeight { height: 550px; }",
89 | "#im-chat #im-chat-send { left: 300px; }" // "display" gets overruled, so use "left" to move it off-screen
90 | );
91 |
92 | // z-index adjustments
93 | $("#qlv_content").css("z-index", "auto"); // will be toggled between 1/auto to bring the chat in front/behind the drop down menus
94 | $("#newnav_top").css("z-index", "2103");
95 | $("ul.sf-menu *").css("z-index", "2102");
96 | $("#lgi_cli").css("z-index", "10003"); // has 1003 and would be overlapped by menu bar items in #newnav_top
97 |
98 | // chat
99 | $("#chatContainer").width(3 + 300 + 3).css("right", RIGHT_MARGIN + "px");
100 | $("#collapsableChat").addClass("bottomDockBar");
101 | $("#qlv_chatControl").css("height", "auto");
102 | $("#qlv_chatControl").addClass("chatBox");
103 |
104 | if (quakelive.cvars.Get(CVAR_chatOverlapIndent).value == "")
105 | quakelive.cvars.Set(CVAR_chatOverlapIndent, 140);
106 |
107 | oldOnResize = window.onresize;
108 | window.onresize = onResize;
109 | oldOnCvarChanged = window.OnCvarChanged;
110 | window.OnCvarChanged = onCvarChanged;
111 | quakelive.mod_friends.FitToParent = updateChatAndContentLayout;
112 |
113 | oldSelectContact = quakelive.mod_friends.roster.SelectContact.bind(quakelive.mod_friends.roster);
114 | quakelive.mod_friends.roster.SelectContact = function(contact) {
115 | oldSelectContact(contact);
116 | updateChatAndContentLayout();
117 | };
118 |
119 | oldRenderMatchDetails = quakelive.matchcolumn.RenderMatchDetails;
120 | quakelive.matchcolumn.RenderMatchDetails = function () {
121 | oldRenderMatchDetails.apply(quakelive.matchcolumn, arguments);
122 | resizeBrowserDetails();
123 | }
124 |
125 | findCssRules();
126 | updateChatAndContentLayout();
127 | }
128 |
129 | function findCssRules() {
130 | var i, j;
131 | for (i = 0; i < document.styleSheets.length; i++) {
132 | var sheet = document.styleSheets[i];
133 | if (!sheet.cssRules) continue;
134 | for (j = 0; j < sheet.cssRules.length; j++) {
135 | try {
136 | var rule = sheet.rules[j];
137 | if (rule.cssText.indexOf("#chatContainer .fullHeight") == 0)
138 | styleFullHeight = rule.style;
139 | }
140 | catch (e) {}
141 | }
142 | }
143 | }
144 |
145 | function onResize(event) {
146 | if (oldOnResize)
147 | oldOnResize(event);
148 |
149 | try { updateChatAndContentLayout(); }
150 | catch (ex) { }
151 | }
152 |
153 | function onCvarChanged(name, val, replicate) {
154 | oldOnCvarChanged(name, val, replicate);
155 | try {
156 | if (name == CVAR_chatOverlapIndent)
157 | updateChatAndContentLayout();
158 | }
159 | catch (e) { }
160 | }
161 |
162 | function updateChatAndContentLayout() {
163 | try {
164 | var $window = $(window);
165 | var width = $window.width();
166 |
167 | // reposition background image and content area
168 | var margin;
169 | var minExpandedWidth = 3 + 1000 + 7 + 3 + 300 + 3 + RIGHT_MARGIN;
170 | if (width <= minExpandedWidth) {
171 | $("body").css("background-position", "-518px 0");
172 | margin = "0 3px 0 3px";
173 | } else if (width <= minExpandedWidth + 7 + 3 + 300 + 3) {
174 | $("body").css("background-position", (-518 + width - minExpandedWidth).toString() + "px 0");
175 | margin = "0 3px 0 " + (width - 1313).toString() + "px";
176 | } else {
177 | $("body").css("background-position", "center top");
178 | margin = "0 auto";
179 | }
180 | $("#qlv_container").css("margin", margin);
181 |
182 | // push profile page and others to the left so they are not hidden by the chat
183 | $("#qlv_contentBody.twocol_left").css("left", Math.min(155, (155 - (minExpandedWidth - 150 - 7 - width))) + "px");
184 |
185 | // modify height of elements that support it
186 | var height = $window.height();
187 | $("div:data(fill-height)").each(function() {
188 | var $this = $(this);
189 | $this.height(height - parseInt($this.data("fill-height")));
190 | });
191 |
192 | // modify height of chat
193 | if (quakelive.IsGameRunning()) {
194 | $("#collapsableChat").css("display", "none"); // hide in-game chat bar
195 | $("#qlv_chatControl").css("display","none");
196 | height = Math.floor(height) * 0.9 - 35; // 10% clock, 35px buttons
197 | } else {
198 | $("#collapsableChat").css("display", "");
199 | $("#qlv_chatControl").css("display", "");
200 | }
201 | height -= 3 + 27 + 14; // height of top border + title bar + bottom bar
202 |
203 | var topOffset = 140; // leave header and menu visible when chat is overlapping the content area
204 | if (width < minExpandedWidth - 7) {
205 | try {
206 | topOffset = parseInt(quakelive.cvars.Get(CVAR_chatOverlapIndent).value);
207 | } catch (e) {
208 | }
209 | height -= topOffset;
210 |
211 | } else {
212 | height -= 7; // leave some gap from top edge
213 | }
214 |
215 | // create more space for "Active Chat"
216 | var footerHeight = 400; // 210 by default
217 | if (height - footerHeight < 300)
218 | footerHeight = height - 300;
219 | $("#im").css("height", "auto");
220 | $("#im-body").height(height - footerHeight);
221 | $("#im-footer").css({ "background": "#222", "padding": "0 5px", height: footerHeight + "px" });
222 | $("#im-chat").css({ "background-clip": "content-box", "height": (footerHeight - 8) + "px" });
223 | $("#im-chat-body").css({ left: 0, top: "13px", width: "284px", "background-color": "white", height: (footerHeight - 8 - 13 - 6 - 33 - 6) + "px" });
224 | $("#im-chat input").css({ width: "282px", left: 0, top: "auto", bottom: "7px" });
225 | $("#im-overlay-body").css({ "background-color": "white", height: (height - 87) + "px" });
226 |
227 | // resize elements which support a dynamic height
228 | if (styleFullHeight)
229 | styleFullHeight.setProperty("height", height + "px");
230 | $("#chatContainer [data-fill]").each(function() {
231 | var $this = $(this);
232 | $this.height(height - parseInt($this.data("fill")));
233 | });
234 |
235 |
236 | // modify z-index to deal with drop-down-menus
237 | $("#qlv_content").css("z-index", topOffset >= 110 ? "auto" : "1"); // #chatContainer has z-index 999
238 | $("#newnav_top").css("z-index", "2103");
239 | $("ul.sf-menu *").css("z-index", "2102");
240 | $("#lgi_cli").css("z-index", "10003");
241 |
242 | resizeBrowserDetails();
243 |
244 | } catch (ex) {
245 | extraQL.log(ex);
246 | }
247 | }
248 |
249 | function resizeBrowserDetails() {
250 | $("#browser_details ul.players.miniscroll").css("max-height", ($(window).height() - 496) + "px");
251 | }
252 |
253 | init();
254 | })();
--------------------------------------------------------------------------------
/source/scripts/attic/rosterGroup.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id qlrostergroup
3 | // @name Roster Group
4 | // @version 1.2
5 | // @namespace Lam
6 | // @author Lam
7 | // @description
8 | // @include http://www.quakelive.com/*
9 | // @exclude http://www.quakelive.com/forum/*
10 | // ==/UserScript==
11 |
12 |
13 | function contentEval(source) {
14 | if ('function' == typeof source) {
15 | source = '(' + source + ')();';
16 | }
17 | var script = document.createElement('script');
18 | script.setAttribute('type', 'application/javascript');
19 | script.textContent = source;
20 | document.body.appendChild(script);
21 | document.body.removeChild(script);
22 | }
23 |
24 |
25 | contentEval(function () {
26 | if (typeof quakelive != 'object') { return; }
27 |
28 | // TODO: make this actually configurable :)
29 | var groupname1 = "huhu";
30 | var groupmembers1str = "Barracouda Bananas NetMaori monja Fillekiller baanze jala1 bakwardsman jaszman macen nathanfadezz niewi rklacks scaretaker somnabulant";
31 | var groupmembers1 = groupmembers1str.toLowerCase().replace(/ /g,',').replace(/,,/g,',').split(',');
32 | var groupname2 = "";
33 | var groupmembers2str = "";
34 | var groupmembers2 = groupmembers2str.toLowerCase().replace(/ /g,',').replace(/,,/g,',').split(',');
35 | var groupname3 = "FFA";
36 | var groupmembers3str = "sya_ oswrap bogotac yakumo crowax gandim nrn437 ghenghis";
37 | var groupmembers3 = groupmembers3str.toLowerCase().replace(/ /g,',').replace(/,,/g,',').split(',');
38 |
39 | // var oldOnRosterUpdated = quakelive.mod_friends.roster.UI_OnRosterUpdated;
40 | // quakelive.mod_friends.roster.UI_OnRosterUpdated = function () {
41 | //
42 | // }
43 |
44 | // this is used by UI_ShowApp to create the roster window
45 | quakelive.mod_friends.TPL_FRIENDS_LIST = quakelive.mod_friends.TPL_FRIENDS_LIST.replace('= 0 )
53 | return 'group1';
54 | else if ( groupmembers2.indexOf( nick.toLowerCase() ) >= 0 )
55 | return 'group2';
56 | else if ( groupmembers3.indexOf( nick.toLowerCase() ) >= 0 )
57 | return 'group3';
58 | return 'online';
59 | }
60 |
61 | var oldImportRosterData = quakelive.mod_friends.roster.ImportRosterData;
62 | quakelive.mod_friends.roster.ImportRosterData = function() {
63 | oldImportRosterData.apply(this, arguments);
64 | //window.console.log("ImportRosterData fired");
65 | for (var i = 0; i < quakelive.mod_friends.roster.fullRoster.length; i++ ) {
66 | var putinto = QLRG_ContactGroup( quakelive.mod_friends.roster.fullRoster[ i ].name );
67 | if ( quakelive.mod_friends.roster.fullRoster[ i ].CanDisplayOnRoster()
68 | && ( putinto != 'online' ) ) {
69 | //window.console.log("ImportRosterData: " + quakelive.mod_friends.roster.fullRoster[ i ].name + ', group: ' + quakelive.mod_friends.roster.fullRoster[ i ].group + '.' );
70 | quakelive.mod_friends.roster.fullRoster[ i ].UI_PlaceInGroup(putinto, true);
71 | /*
72 | * contact.StartInactivityTimeout() has hardcoded group to which the contact
73 | * returns after 3 minutes (length also hardcoded). Best thing to do would be
74 | * to override contact.UI_PlaceInGroup to automatically change 'online' to
75 | * my group, but I'm too noob to do that.
76 | */
77 | quakelive.mod_friends.roster.fullRoster[ i ].StartInactivityTimeout = function(){
78 | if (this.timeoutHandle) {
79 | clearTimeout(this.timeoutHandle)
80 | }
81 | var contact = this;
82 | this.timeoutHandle = setTimeout(function() {
83 | if (quakelive.mod_friends.roster.selectedContact != contact) {
84 | if (contact.group == 'active') {
85 | if (contact.CanDisplayOnRoster()) {
86 | contact.UI_PlaceInGroup(QLRG_ContactGroup(contact.name))
87 | } else {
88 | contact.UI_RemoveFromGroup()
89 | }
90 | contact.timeoutHandle = null
91 | }
92 | } else {
93 | contact.StartInactivityTimeout()
94 | }
95 | }, 180 * 1000)
96 | };
97 | }
98 | }
99 | }
100 |
101 | var OldPresence = IM_OnPresence;
102 | IM_OnPresence = function(presence_json) {
103 | OldPresence.apply(this, arguments);
104 | var pres = quakelive.Eval(presence_json);
105 | if (pres) {
106 | var jid = new JID(pres.who);
107 | //window.console.log("Test 2: " + jid.username);
108 | if ( QLRG_ContactGroup( jid.username ) != 'online' ) {
109 | var contact = quakelive.mod_friends.roster.GetContactByJID(jid);
110 | if (contact) {
111 | //window.console.log('Test 3');
112 | if (contact.CanDisplayOnRoster()) {
113 | //window.console.log('Prev group: ' + contact.group + '.');
114 | contact.UI_PlaceInGroup(QLRG_ContactGroup(contact.name));
115 | // same as above
116 | contact.StartInactivityTimeout = function(){
117 | if (this.timeoutHandle) {
118 | clearTimeout(this.timeoutHandle)
119 | }
120 | var contact = this;
121 | this.timeoutHandle = setTimeout(function() {
122 | if (quakelive.mod_friends.roster.selectedContact != contact) {
123 | if (contact.group == 'active') {
124 | if (contact.CanDisplayOnRoster()) {
125 | contact.UI_PlaceInGroup(QLRG_ContactGroup(contact.name))
126 | } else {
127 | contact.UI_RemoveFromGroup()
128 | }
129 | contact.timeoutHandle = null
130 | }
131 | } else {
132 | contact.StartInactivityTimeout()
133 | }
134 | }, 180 * 1000)
135 | };
136 | }
137 | }
138 | }
139 | }
140 | }
141 | });
142 |
143 |
144 | extraQL.addStyle( "#im-group1, #im-group2, #im-group3 { display: none }'" );
145 |
146 | extraQL.addStyle( '#im-group1 .itemlist, #im-group2 .itemlist, #im-group3 .itemlist {\
147 | color: #000000; display: block; font-family: Arial; font-size: 12px;\
148 | font-style: normal; list-style: none outside none; margin: 0;\
149 | padding: 0; text-align: center; top: 0; vertical-align: text-top; }' );
150 |
151 | extraQL.addStyle( '#im-group1 h1, #im-group2 h1, #im-group3 h1 { display: block;\
152 | height: 21px; margin: 0; padding: 0;\
153 | text-align: left; width: 300px; padding-left: 30px; color:black;\
154 | text-shadow: 0.1em 0.1em 0.2em white; font-family: Arial; font-style: italic;\
155 | font-size:12px;\
156 | background: url("data:image/jpeg;base64,\
157 | /9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAIBAQEBAQIBAQECAgICAgQDAgICAgUEBAMEBgUGBgYF\
158 | BgYGBwkIBgcJBwYGCAsICQoKCgoKBggLDAsKDAkKCgr/2wBDAQICAgICAgUDAwUKBwYHCgoKCgoK\
159 | CgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgr/wAARCAAVASwDAREA\
160 | AhEBAxEB/8QAHAAAAgMAAwEAAAAAAAAAAAAAAwQAAQIFBwgJ/8QAaBAAAAIHAQMLGBQOAwAAAAAA\
161 | AQIAAwQFBgcIEQkXIRIxMkFkcYWV09TVExQVGBkiQlZXWGFlgZOWoqaxxdHS4eTlIyU0NTY4UVRV\
162 | ZnJ1goOUo6XE4uMmJygzR0hSY3N0hKHBw4akwv/EABgBAQEBAQEAAAAAAAAAAAAAAAABAgME/8QA\
163 | HhEBAAMBAQACAwAAAAAAAAAAAAECEhETITFBUVL/2gAMAwEAAhEDEQA/AOwqYapK0X1LZmfkV1Kz\
164 | JaCkZRWtDW8X83nxBQC0TGOd2ZQY4iKemK0HZjDVxPFvaVbvYarYjXr1y0FalSqihcY5jCNgFAAd\
165 | 4CIiKazX+RzZp3VUAUdqTojEQ9Q7xaBD+7ICZ5UYGeFUWXOSK/l67WyTNBCTvqhEbRnLFfNb12oA\
166 | lzQ/A6qd9ToY84om+E3rdTBLmv6DCqelThLPxvRAPum04/8AhLFacDKuf9TiuwTTVfRs9rP3KXFA\
167 | wpqKqVIG/TIewhyWk6Sa1DKqpKowtgHj16j/AFh0mKgxalqhih5HHj3Ka3HK3LA6woxUbLU5UUXJ\
168 | TBffI8tFqPOgIWqCoMclHz9HRJajzoDFqgn5wUeP3TJb20edBstUM8gycfv7TJb20edAYtUU6MSG\
169 | Kj1+W++S3to86DCyqGd1o4mP37nbklvdI86AR6oZ75UfvzmPJb3SPOgEeqCfwYSx8/dM1vdI86AR\
170 | qnqhhwFmA/Q0UW9tHnQDGpuo7gZiPsNFFvbR50GDVL1LjZiZkPsNElvbR50ATVJVODkZoP0NEFqX\
171 | lAI1RlUJv0sP4OSVuW9yKXNAI1QdU58F+CIgzm5b3AozQDWT9qoHHnRE4e5eC3U0YoBGnrVQONO6\
172 | LcH7LyXaijFAM08KqjY07owxsp6L/wDDOKTNAI07KruBnfG3MejV/hkFHKDAzoqy4t0efBeTXrAU\
173 | coQzfpq3ttv2x/h5YNmxgpjNBL9NW/Fsj/TBs2LS5oJfpq34tkf6YNmxaM0FGnRVxbgnZMDTFt2K\
174 | Rmgl+irji1zB0xbdikZoJfoq44tcwdMW3YpGaBV9Tuq9UOteuVTumCUxSWgIPJtCzk+dSMUHieoS\
175 | te6Bw/M5sdjsq0nAzKSEKJFLLFb0VFLhHgSu0At5NiTNB6VoWkOyxDJtgZ2uCSrFC9jxC0h4fEQO\
176 | UQsMA+UwY4CmYkdlS/pIi1XHi93RRCbed2uAwGYmtqda8yt5mMUBUnADsBwWCqKI4u1WrsXFIJcA\
177 | JrY7LPIvaSkTg4wLZysAMv8Aki9dJ2AG8rypD5CGtkdgHZpJWj51gH9IAfVwR2BybJJABw7jg+T2\
178 | f6gRqIDquRgGALGLD/C+wmtwDEkXh8xgHxf2UbgGJIkRDCyBzvvI3A2EircZl6RG4EGROJw7aW/A\
179 | tRvv0KvFm9a/N95G4EvF5jDnfeRuBd4wfWgc77yNwJeI/cfN95G4ELIgcfbXpUbgavFZm6RG4EvF\
180 | Zm6RG4EvFZm6RG4FGkTaHmXpUbgVeJzP82jcCXjB9aBzvvI3Aq8QJg8x/N/ZRuBg0iTY22NvxP2B\
181 | RuAM0iMwf9f7sUbgYPIc+MDssszL9yjcDBpDhb51j8j+4FG4AVkhx9hbbcwiP1UUmwqukIPC/boX\
182 | b9QFJ2AmukAfhWt0Ft7FijsBZZT8a30I2/8AH7ew4o7AzeANwodTviZHYEvAG4UOp3xMjsCt19HL\
183 | g/qd8TI7Am6++0/qd8To7Am6++0/qd8To7ASiOn4CORpOMHjYCu30Pcn3nS6HzKq8lWRinc8Gc0K\
184 | GKIKiWgDgHDhNylTHZHf9B9ekFwrKl0sJ5AbciDOXfweTtJbzNxQ9dMV+x6XUXRqDMH5Odmc+HcH\
185 | YpNAwXQt2LVYqmWRqlWcciY72ZxAOYViKP8AdJM8Fbv2biPsemBNbpNA7PX0IGwSeY8b2RJqCXvw\
186 | HFV0NaGYN8k4xjnvMNRSi1l0pbVAWhJVhGzlsOooCx7qQ81AWkka7xHkvg2pICC+60RApHfJFOzm\
187 | vg+pICDXde4vUjYqkc6AwZb1WD/rSdCSq7JRYxNG1nnId1tKkAs2iqfJ1Y25Q4oVRuslEaLtY3K8\
188 | jTQxdEw62QANF2vfJQtLTgwBnREOtkBRZdsolD83T2wF0f8ABkAC+7YxgGQkKwBnPzwdARartpHq\
189 | ppBWSSDAACHs14OgHd92zjlfvyyRrCOjng6A6pu2cWWWmkA7zZ7+HW6A0z3bKI1g2Hp5YBw8MI63\
190 | QGVF2te58lTew9Eg62QG2W7UNa7J00MfRQbWyA0zXZ/bgLFlMjN0VjrVAZV3ZJjW5OmNXzIu8DQD\
191 | q7sG61gABqZC4cf8LS6yQGAutziXCGKpmszosV6wQCkuqsPrf1bRLgyopVawQChdQodPbbTkPRMo\
192 | 1ggUe6aQ4fJU7dUTNsegLrbpJCq3JU7jp8xj13agJrbo3Bxw9Lr9Mu8exaAse6MwWYuKCnIA0Wdo\
193 | 9ikDO9GIM43Mumrt2JQJvRiDONzLpq7diUAZro7BID6XH6XdmxKBW9HYJ43D6XdmxCBN6OwTxuH0\
194 | u7NiEBCKbo5BAuFpLuuI5DKe7s9X3oRP0PmXV5VxCD6na3vAsmtogdSTyMGp2jZhNl7jATmP/9k="\
195 | ) no-repeat scroll 0 0 transparent; }' );
196 |
197 |
--------------------------------------------------------------------------------
/source/scripts/attic/samPresets.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Quake Live Start-a-Match quick preset access
3 | // @description Adds links to load your saved presets directly from the start-a-match screen
4 | // @author PredatH0r
5 | // @version 1.2
6 | // @include http://*.quakelive.com/*
7 | // @exclude http://*.quakelive.com/forum*
8 | // @unwrap
9 | // ==/UserScript==
10 |
11 | /*
12 |
13 | Version 1.2
14 | - updated to work with latest UI changes
15 |
16 | Version 1.1
17 | - updated extraQL script url to sourceforge
18 |
19 | */
20 |
21 | (function() {
22 | var amplify = window.amplify;
23 | var quakelive = window.quakelive;
24 |
25 | function init() {
26 | extraQL.addStyle(
27 | "#quickSamPresets { background-color: #AAA; color: black; padding: 3px 15px; }",
28 | "#quickSamPresets a { color: black; }"
29 | );
30 | quakelive.AddHook("OnContentLoaded", onContentLoaded);
31 | onContentLoaded();
32 | }
33 |
34 | function onContentLoaded() {
35 | if (!quakelive.activeModule || quakelive.activeModule.GetTitle() != "Create Match")
36 | return;
37 | if ($("#quickSamPresets").length > 0)
38 | return;
39 |
40 | var html = "
";
41 | var presets = amplify.store("sam_presets") || {};
42 | var count = 0;
43 | $.each(presets, function(name) {
44 | html += count++ == 0 ? "Load preset: " : " | ";
45 | html += "
" + extraQL.escapeHtml(name) + "";
46 | });
47 | if (count == 0)
48 | html += "No saved presets available";
49 | html += "
";
50 | $("#mod_sam").prepend(html);
51 | $("#quickSamPresets a").click(function () { quakelive.mod_startamatch.loadPreset($(this).text()); });
52 | }
53 |
54 | init();
55 | })(window);
56 |
57 |
--------------------------------------------------------------------------------
/source/scripts/attic/specHelper.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id 153714
3 | // @name QuakeLive Speccing Helper
4 | // @version 1.1
5 | // @description Autoexec commands on spectator connect, reconnect on errors
6 | // @namespace qlspec
7 | // @homepage http://esreality.com
8 | // @author simonov
9 | // @contributor wn
10 | // ==/UserScript==
11 |
12 | // Don't bother if Quake Live is down for maintenance
13 | if (/offline/i.test(document.title)) {
14 | return;
15 | }
16 |
17 | // Make sure we're on top
18 | if (window.self != window.top) {
19 | return;
20 | }
21 |
22 | // Set up some utility things
23 | var DEBUG = true,
24 | DOLITTLE = function() {},
25 | logMsg = DEBUG ? function(aMsg) {
26 | console.log("QLSPEC: " + aMsg)
27 | } : DOLITTLE,
28 | logError = function(aMsg) {
29 | console.log("QLSPEC ERROR: " + aMsg)
30 | };
31 |
32 | /**
33 | * Override OnCvarChanged
34 | */
35 | QLSPEC = {
36 | // Debug
37 | DEBUG: false,
38 |
39 | // cache
40 | VARS: {},
41 | TIMES: {},
42 |
43 | // vars
44 | TIMEOUT: 10000, // timeout to check for connection error
45 | }
46 |
47 | // make a spec actions
48 | QLSPEC.do = function(cvar, delay) {
49 | // do spectator actions only if asked to
50 | if (quakelive.cvars.GetIntegerValue("_qlspec_active", 0) != 1) {
51 | return;
52 | }
53 |
54 | logMsg("spectating commands via " + cvar);
55 | qz_instance.SendGameCommand('vstr _qlspec_do;');
56 |
57 | // change pov to make sure we are spectating a person, not a wall
58 | window.setTimeout(function() {
59 | qz_instance.SendGameCommand('vstr _qlspec_pov;');
60 | }, delay);
61 |
62 | // change pov to next player in a given period of time
63 | var next_pov_time = quakelive.cvars.GetIntegerValue("_qlspec_nextpovtime", 0);
64 | if (next_pov_time > 0) {
65 | window.setTimeout(function() {
66 | qz_instance.SendGameCommand('vstr _qlspec_pov;');
67 | }, next_pov_time * 1000);
68 | }
69 | }
70 |
71 | var oldOnCvarChanged = OnCvarChanged;
72 | OnCvarChanged = function(name, val, replicate) {
73 | //logMsg(name + " = " + val);
74 | var timeout = +new Date() - QLSPEC.TIMEOUT;
75 | switch (name) {
76 | case "com_errorMessage":
77 | QLSPEC.VARS[name] = val;
78 | QLSPEC.TIMES[name] = +new Date();
79 | break;
80 |
81 | case "sv_cheats":
82 | // sv_cheats goes to 1 after appearing of an error message
83 | if (qz_instance.IsGameRunning() && val == 1 && typeof(QLSPEC.TIMES["com_errorMessage"]) != "undefined" && QLSPEC.TIMES["com_errorMessage"] - timeout > 0) {
84 | if (
85 | // manual disconnect from server
86 | (QLSPEC.VARS["com_errorMessage"].indexOf("Disconnected from server") >= 0)
87 | // kicked from server
88 | || (QLSPEC.VARS["com_errorMessage"].indexOf(" kicked") >= 0)
89 | ) {
90 | logMsg('kicked or disconnected, no need to reconnect');
91 | break;
92 | }
93 | logMsg('reconnecting');
94 | qz_instance.SendGameCommand('reconnect;');
95 | } else if (val == 0) {
96 | // game started
97 | QLSPEC.VARS[name] = val;
98 | QLSPEC.TIMES[name] = +new Date();
99 | if (quakelive.cvars.GetIntegerValue("cg_spectating") == 1) {
100 | QLSPEC.do(name, 5000);
101 | }
102 | }
103 | break;
104 |
105 | case "cg_spectating":
106 | if (qz_instance.IsGameRunning() && val == 1 && typeof(QLSPEC.TIMES["sv_serverid"]) != "undefined" && QLSPEC.TIMES["sv_serverid"] - timeout > 0) {
107 | QLSPEC.do(name, 1000);
108 | }
109 | break;
110 |
111 | case "sv_serverid":
112 | case "gt_eventid":
113 | QLSPEC.VARS[name] = val;
114 | QLSPEC.TIMES[name] = +new Date();
115 | if (qz_instance.IsGameRunning() && quakelive.cvars.GetIntegerValue("cg_spectating") == 1) {
116 | QLSPEC.do(name, 1000);
117 | }
118 | break;
119 | }
120 | oldOnCvarChanged.call(null, name, val, replicate);
121 | }
122 |
--------------------------------------------------------------------------------
/source/scripts/attic/startpage.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id startpage
3 | // @name Start Page
4 | // @description Opens QL with your preferred start page. Set any page as your start page with "Userscripts / Set Start Page"
5 | // @author PredatH0r
6 | // @version 1.0
7 | // @unwrap
8 | // ==/UserScript==
9 |
10 | (function () {
11 | function init() {
12 | var home = quakelive.cvars.Get("web_home");
13 | if (home && home.value)
14 | window.location.href = "/#!" + home.value;
15 |
16 | HOOK_MANAGER.addMenuItem("Set as Start Page", setStartPage);
17 | }
18 |
19 | function setStartPage() {
20 | var page = window.location.hash;
21 | if (page.length > 2) {
22 | page = page.substr(2);
23 | quakelive.cvars.Set("web_home", page);
24 | }
25 | }
26 |
27 | init();
28 | })();
--------------------------------------------------------------------------------
/source/scripts/attic/streamNotifier.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id 114449
3 | // @name Quake Live Stream Notifier
4 | // @version 0.67
5 | // @namespace phob.net
6 | // @author wn
7 | // @contributor cityy
8 | // @description Displays a list of live gaming streams on QuakeLive.com and ESReality.com
9 | // @include http://esreality.com/*
10 | // @include http://*.esreality.com/*
11 | // @exclude http://*.esreality.com/w*
12 | // @include http://esreality.net/*
13 | // @include http://*.esreality.net/*
14 | // @include http://*.quakelive.com/*
15 | // @exclude http://*.quakelive.com/forum*
16 | // @run-at document-end
17 | // @updateURL https://userscripts.org/scripts/source/114449.meta.js
18 | // ==/UserScript==
19 |
20 |
21 | /**
22 | * Only run if we're in the top frame
23 | */
24 | if (window.self != window.top) {
25 | return;
26 | }
27 |
28 |
29 | /**
30 | * Global stuff
31 | */
32 | var DOLITTLE = function() {}
33 | , GM_registerMenuCommand = GM_registerMenuCommand ? GM_registerMenuCommand : DOLITTLE
34 | , UPDATE_MS = 3E5 // 5 * 60 * 1000
35 | , RE_okayChars = /^[\w .,`~!@#&*:\/\\\-=+?{}\(\)\[\]\|]+$/
36 | , RE_url = /^[\w .:\/\\\-=+?#&]+$/
37 | , isESR = /^http:\/\/(?:\w+\.)?esreality\.(?:com|net)\/.*$/i.test(window.location.href)
38 | , src = "http://phob.net/qlsn_quake_streams.json"
39 | , jsonReq
40 | ;
41 |
42 |
43 | /**
44 | * Helper to add CSS
45 | */
46 | function addStyle(aContent) {
47 | if ("[object Array]" === Object.prototype.toString.call(aContent))
48 | aContent = aContent.join("\n");
49 | var s = document.createElement("style");
50 | s.type = "text/css";
51 | s.textContent = aContent;
52 | document.body.appendChild(s);
53 | }
54 |
55 |
56 | /**
57 | * Add the live stream bar
58 | */
59 | addStyle(
60 | "#qlsn_bar {margin: 0 auto; padding: 2px; background-color: "
61 | + (localStorage["qlsn_barColor"] || "#000") + "; clear: both;}"
62 | + "#qlsn_bar em {color: #ccc; font-style: italic;}"
63 | + "#qlsn_bar strong {font-weight: bold;}"
64 | + "#qlsn_bar a {text-decoration: none;}"
65 | + "#qlsn_bar a.qlsn_stream {color: #ccc;}"
66 | + "#qlsn_bar a.qlsn_stream:hover {color: #fff;}"
67 | );
68 |
69 | // Styling unique to ESR or QL
70 | if (isESR)
71 | addStyle("#qlsn_bar {font-size: 11px;}");
72 | else
73 | addStyle("#qlsn_bar strong, #qlsn_bar strong a {color: #fc0;}");
74 |
75 | var qlsnBar = document.createElement("div");
76 | qlsnBar.setAttribute("id", "qlsn_bar");
77 | qlsnBar.innerHTML = "
loading live streams…";
78 | document.body.insertBefore(qlsnBar, document.body.firstChild);
79 |
80 |
81 | /**
82 | * Potential sources in order of priority
83 | * NOTE: Intentionally duplicated the source so we try it again if the first
84 | * request fails, since it is quite likely something other than site
85 | * downtime caused the failure.
86 | */
87 | var listSources = [src, src];
88 |
89 |
90 | /**
91 | * Updates the live stream bar
92 | * @param {Array} an array of live streams
93 | */
94 | function updateBar(liveStreams) {
95 | var x = []
96 | , bar = document.getElementById("qlsn_bar")
97 | ;
98 |
99 | // Stop if we don't have any live streams
100 | if (!liveStreams.length) {
101 | bar.innerHTML = "
no live streams found";
102 | return;
103 | }
104 |
105 | // Sort by stream name
106 | liveStreams.sort(function(a, b) {
107 | a = a.name.toLowerCase(), b = b.name.toLowerCase();
108 | return (a < b ? -1 : a > b ? 1 : 0);
109 | });
110 |
111 | // Generate and display the list
112 | for (var i = 0, e = liveStreams.length; i < e; ++i) {
113 | if (!(RE_okayChars.test(liveStreams[i].name)
114 | && RE_okayChars.test(liveStreams[i].game)
115 | && RE_url.test(liveStreams[i].url)
116 | && !isNaN(parseInt(liveStreams[i].viewers)))) {
117 | console.log("Unexpected value(s) in: " + JSON.stringify(liveStreams[i]));
118 | continue;
119 | }
120 | x.push("
"
124 | + liveStreams[i].name + "");
125 | }
126 |
127 | if (0 == x.length) {
128 | x = ["(error parsing stream list)"];
129 | }
130 |
131 | bar.innerHTML = "
LIVE NOW: "
133 | + x.join(" | ");
134 | }
135 |
136 |
137 | /**
138 | * Get a new list of live streams.
139 | * Tries the fallback source if the first results in an error.
140 | * @param {Integer} an optional index specifying the list source to try
141 | * @param {Boolean} an optional flag to force a refresh
142 | */
143 | function refreshList(aIndex, aForce) {
144 | // If we're within UPDATE_MS of the last good refresh, try to load from cache.
145 | // Currently only doing this for ESR, since QL doesn't actually reload often.
146 | var liveStreams
147 | , now = (new Date()).getTime()
148 | , fetchCache = localStorage["qlsn_fetchCache"]
149 | , lastFetch = Number(localStorage["qlsn_lastFetch"])
150 | ;
151 |
152 | if (isESR && !aForce && fetchCache && lastFetch
153 | && (now - lastFetch < UPDATE_MS)) {
154 | try {
155 | fetchCache = JSON.parse(fetchCache);
156 | updateBar(fetchCache);
157 | return;
158 | }
159 | catch(e) {}
160 | }
161 |
162 | // Starting fresh
163 | liveStreams = [];
164 | localStorage["qlsn_fetchCache"] = "";
165 |
166 | // Default to the first source if not specified
167 | aIndex = aIndex || 0;
168 |
169 | // Make sure the index specified is valid, otherwise stop trying
170 | if ("undefined" == typeof listSources[aIndex]) {
171 | updateBar(liveStreams);
172 | return;
173 | }
174 |
175 | // Try updating with the current list source.
176 | // If it fails, retry using the next one.
177 | jsonReq = new XMLHttpRequest();
178 | jsonReq.onreadystatechange = function() {
179 | // Done?
180 | if (jsonReq.readyState === 4) {
181 | // Good HTTP response?
182 | if (200 == jsonReq.status) {
183 | try {
184 | liveStreams = JSON.parse(jsonReq.responseText);
185 | }
186 | catch(e) {
187 | console.log("Error parsing response from " + listSources[aIndex]
188 | + " (" + e + ")");
189 | refreshList(aIndex+1, aForce);
190 | return;
191 | }
192 | localStorage["qlsn_fetchCache"] = JSON.stringify(liveStreams);
193 | localStorage["qlsn_lastFetch"] = now;
194 | updateBar(liveStreams);
195 | }
196 | // Bad HTTP response...
197 | else {
198 | console.log("Error requesting JSON from " + listSources[aIndex]
199 | + " (" + jsonReq.statusText + ")");
200 | refreshList(aIndex+1, aForce);
201 | }
202 | }
203 | }
204 | jsonReq.open("GET", listSources[aIndex] + "?" + now, true)
205 | jsonReq.send();
206 | }
207 |
208 |
209 | /**
210 | * Add a menu command to change the bar's background color
211 | */
212 | GM_registerMenuCommand("Quake Live Stream Notifier: Change the bar color", function() {
213 | var color = prompt("Enter a CSS-friendly color without quotation marks.\n\n"
214 | + "For example (without quotation marks): 'red' OR '#621300'\n\n"
215 | , (localStorage["qlsn_barColor"] || "#000"));
216 | if (!color) return;
217 |
218 | localStorage["qlsn_barColor"] = color;
219 | alert("Value set. Please reload Quake Live.");
220 | });
221 |
222 |
223 | /**
224 | * Add a menu command to force a list refresh
225 | */
226 | GM_registerMenuCommand("Quake Live Stream Notifier: Force list reload", function() {
227 | refreshList(null, true);
228 | });
229 |
230 |
231 | /**
232 | * Update the list every UPDATE_MS
233 | */
234 | window.setInterval(function(){refreshList()}, UPDATE_MS);
235 |
236 |
237 | /**
238 | * Load the list for the first time
239 | */
240 | refreshList();
241 |
--------------------------------------------------------------------------------
/source/scripts/attic/toolTip.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id 395655
3 | // @name Quake Live ToolTip Fix
4 | // @description Shows a ToolTip popup when hovering the mouse over a UI element with additional information. E.g. QLranks Elo information, weapon accuracy, ...
5 | // @author PredatH0r
6 | // @version 1.3
7 | // @unwrap
8 | // ==/UserScript==
9 |
10 | /*
11 |
12 | Description:
13 | If you hover your mouse over UI elements which have additional information, that info
14 | will be shown in a ToolTip popup.
15 |
16 | This standard web browser feature is somehow broken in the QuakeLive standalone client.
17 | This script takes care of displaying this information text.
18 |
19 | e.g. Server modification, QLranks Elo information, weapon accuracy, ...
20 |
21 | Version 1.3:
22 | - Fixed ToolTip position when window is scrolled
23 | - ToolTip now activated, when QL is started fullscreen
24 | - ToolTip is now showing on top of other elements (like player list popup)
25 |
26 | Version 1.2.1:
27 | Fixed issue with empty ToolTip text
28 |
29 | Version 1.2:
30 | This alternative ToolTip is activated only after game-mode was entered, which is what breaks the QL built-in
31 | tool tips. The alternative ToolTip can manually be enabled/disabled with cvar web_tooltip.
32 |
33 | Version 1.1:
34 | Rewritten based on tips from 'wn'.
35 |
36 | Version 1.0:
37 | First release
38 |
39 | */
40 |
41 | (function () { // scope
42 | var $tooltip;
43 | var enabled = false;
44 | var oldOnCvarChanged;
45 |
46 | function init() {
47 | $("head").append(
48 | '');
51 |
52 | $("body").append('
');
53 | $tooltip = $("#ttip");
54 |
55 | // activate the ToolTip when it was loaded through the QLHM UI or we are in fullscreen mode
56 | if ($("#qlhmPrompt").length > 0 || quakelive.cvars.Get("r_fullscreen").value == "1")
57 | enable();
58 | else
59 | disable();
60 |
61 | // install hook to react on changed cvar "web_tooltip"
62 | oldOnCvarChanged = window.OnCvarChanged;
63 | window.OnCvarChanged = OnCvarChanged;
64 |
65 | // The standard QL tool-tip implementation breaks when game mode is started.
66 | // So when game mode is started, activate the alternative tool tip
67 | quakelive.AddHook("OnGameModeStarted", enable);
68 | }
69 |
70 | function OnCvarChanged(name, val, replicate) {
71 | try {
72 | var lower = name.toLowerCase();
73 | if (lower == "web_tooltip") {
74 | if (val == "0")
75 | disable();
76 | else
77 | enable();
78 | }
79 | }
80 | catch (e) { }
81 | oldOnCvarChanged.call(null, name, val, replicate);
82 | }
83 |
84 | function enable() {
85 | quakelive.cvars.Set("web_tooltip", 1);
86 | if (!enabled) {
87 | enabled = true;
88 | $("body").on("hover", "[title]", onHover);
89 | }
90 | }
91 |
92 | function disable() {
93 | quakelive.cvars.Set("web_tooltip", 0);
94 | if (enabled) {
95 | enabled = false;
96 | $("body").off("hover", "[title]", onHover);
97 | hideTip();
98 | }
99 | }
100 |
101 | function onHover(e) {
102 | if ("mouseenter" === e.type)
103 | showTip(e.pageX, e.pageY, this.title);
104 | else
105 | hideTip();
106 | }
107 |
108 | function showTip(x, y, text) {
109 | $tooltip.empty();
110 | $tooltip.append(text);
111 | if (text && text.trim() != "")
112 | $tooltip.css({ "left": x + 16, "top": y + 16, "display": "block" });
113 | }
114 |
115 | function hideTip() {
116 | $tooltip.css("display", "none");
117 | }
118 |
119 | init();
120 | })(window);
121 |
122 |
--------------------------------------------------------------------------------
/source/scripts/attic/twitter.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name QL Twitter integration
3 | // @version 1.2
4 | // @author PredatH0r
5 | // @description Show @quakelive tweets in chat bar
6 | // @unwrap
7 | // ==/UserScript==
8 |
9 | /*
10 |
11 | This script integrates the official "@quakelive" Twitter channel as a separate tab in the QL chat window.
12 | If the "QL Chat Dock" script is also installed, the Twitter popup will automatically adjust to the QL window's size.
13 |
14 | Version 1.2
15 | - ensuring consistent order of tabs in the chat bar
16 |
17 | Version 1.1
18 | - updated extraQL script url to sourceforge
19 |
20 | Version 1.0
21 | - first public release
22 |
23 | */
24 |
25 | (function () {
26 | // external variables
27 | var quakelive = window.quakelive;
28 | var extraQL = window.extraQL;
29 |
30 | function init() {
31 | // delay init so that twitch, twitter, ESR and IRC scripts add items to chat menu bar in a defined order
32 | if (extraQL.hookVersion) // introduced at the same time as the addTabPage() "priority" param
33 | delayedInit();
34 | else
35 | setTimeout(delayedInit, 800);
36 | }
37 |
38 | function delayedInit() {
39 | onContentLoaded();
40 | quakelive.AddHook("OnContentLoaded", onContentLoaded);
41 |
42 | // the resizeLayout script's event handler will resize the
for us
43 | if (typeof (window.onresize) == "function")
44 | window.onresize();
45 | }
46 |
47 | function onContentLoaded() {
48 | if ($("#twitterFeed").length)
49 | return;
50 |
51 | extraQL.addStyle("#twitterFeed { width: 300px; background-color: white; display: none; }");
52 |
53 | var html =
54 | '' +
55 | '';
56 |
57 | var page = "";
58 | extraQL.addTabPage("twitterFeed", "Twitter", page, showTwitterTab, 200);
59 | }
60 |
61 | function showTwitterTab() {
62 | $("#twitter-widget-0").addClass("fullHeight"); // this enables automatic resizing via resizeLayout.usr.js
63 | extraQL.showTabPage("twitterFeed");
64 | }
65 |
66 | init();
67 | })();
68 |
--------------------------------------------------------------------------------
/source/scripts/attic/weightedAcc.usr.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id 99947
3 | // @name Quake Live Weighted Accuracy
4 | // @namespace http://userstyles.org
5 | // @description A better accuracy measurement for QuakeLive.
6 | // @include http://*.quakelive.com/*
7 | // @include http://*.quakelive.com/profile/statistics/*
8 | // ==/UserScript==
9 |
10 | (function () {
11 | return; // this script is broken as of 2014-04-28
12 |
13 | function weightedacc(event) {
14 | var totalweightedacc = 0; // The final result of the calculation.
15 | //var reloadtimes = [400, 100, 1000, 800, 800, 50, 1500, 100, 300, 50, 1000, 800]; // Weapon reload times (msec).
16 | //var totalreloadtime = reloadtimes.reduce(function(a, b){ return a+b;}); // Calculate the total of the reload times.
17 | //var averagereloadtime = totalreloadtime/reloadtimes.length;
18 |
19 | // Make sure the proper type of element is being inserted, and this hasn't already been called on this page.
20 | if (event.target.tagName != "DIV"
21 | && event.target.className != 'prf_weapons'
22 | || document.getElementById('weightedacc')) {
23 | return;
24 | }
25 |
26 | // Calculate a new, "weighted" accuracy.
27 | var weapselem = document.getElementsByClassName('prf_weapons cl')[0];
28 | var accelems = weapselem.getElementsByClassName('text_tooltip'); // userscripts.org/topics/77188
29 | var useelems = weapselem.getElementsByClassName('col_usage');
30 |
31 | var i;
32 | for (i = 0; i < accelems.length; i++) { // Foreach weapon, ignore gaunt.
33 | acc = accelems[i].innerHTML.substr(0, accelems[i].innerHTML.length - 1) * 1;
34 | use = useelems[i + 1].innerHTML.substr(0, useelems[i + 1].innerHTML.length - 1) * 1; // userscripts.org/topics/77188
35 |
36 | totalweightedacc += (acc * use/100);
37 | //totalweightedacc += (acc * reloadtimes[i]/averagereloadtime);
38 | }
39 |
40 | // Display this new value.
41 | var vitals = document.getElementsByClassName('prf_vitals')[0];
42 | var info = vitals.getElementsByTagName('p')[0];
43 | info.innerHTML += "Weighted Accuracy: "
44 | + totalweightedacc.toFixed(1) + "%
";
45 | }
46 |
47 | document.body.addEventListener("DOMNodeInserted", weightedacc, true);
48 |
49 | })();
--------------------------------------------------------------------------------
/source/scripts/autoExec.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name AutoExec: Automate actions when you start/leave a match.
3 | // @version 2.1
4 | // @author PredatH0r
5 | // @description Toggles fullscreen and executes commands when you start/leave a game.
6 | // @description /r_autoFullscreen: 1=fullscreen when playing, 2=windowed when not, 3=both.
7 | // @description gamestart.cfg and gameend.cfg are executed when joining/leaving a match.
8 | // @description You can use /steamnick in these config files to add color to your name.
9 | // @enabled 1
10 | // ==/UserScript==
11 |
12 | /*
13 |
14 | Version 2.1
15 | - create r_autoFullscreen cvar if not set
16 |
17 | Version 2.0
18 | - rewrite to work with Steam exclusive version of Quake Live
19 |
20 | */
21 |
22 | (function () {
23 | // external global variables
24 | var qz_instance = window.qz_instance;
25 | var console = window.console;
26 | var lastGameEndTimestamp;
27 |
28 | function init() {
29 | // create cvar
30 | var autoFullscreen = qz_instance.GetCvar("r_autoFullscreen");
31 | if (!autoFullscreen)
32 | qz_instance.SetCvar("r_autoFullscreen", "0");
33 |
34 | var postal = window.req("postal");
35 | var channel = postal.channel();
36 | channel.subscribe("game.start", onGameStart);
37 | channel.subscribe("game.end", onGameEnd);
38 | echo("^2autoExec.js installed");
39 | }
40 |
41 | function log(msg) {
42 | console.log(msg);
43 | }
44 |
45 | function echo(msg) {
46 | msg = msg.replace(/\"/g, "'").replace(/[\r\n]+/g, " ");
47 | qz_instance.SendGameCommand("echo \"" + msg + "\"");
48 | }
49 |
50 | function onGameStart() {
51 | echo("^3autoExec.js: executing gamestart.cfg");
52 | qz_instance.SendGameCommand("exec gamestart.cfg");
53 | autoSwitchFullscreen(1);
54 | }
55 |
56 | function onGameEnd() {
57 | var now = Date.now();
58 |
59 | // QL sends 2x "game.end" notifications and r_fullscreen is latched between the two, so we don't know the real state
60 | // this hack ignores the 2nd if it's within 2 seconds of the first
61 | if (lastGameEndTimestamp + 2000 > now)
62 | return;
63 | lastGameEndTimestamp = now;
64 |
65 | echo("^3autoExec.js: executing gameend.cfg");
66 | qz_instance.SendGameCommand("exec gameend.cfg");
67 | autoSwitchFullscreen(0);
68 | }
69 |
70 | function autoSwitchFullscreen(enter) {
71 | var auto = qz_instance.GetCvar("r_autoFullscreen");
72 | var mask = enter ? 0x01 : 0x02;
73 | if (auto != "" && (parseInt(auto) & mask) && qz_instance.GetCvar("r_fullscreen") != enter) {
74 | //log("autoExec.js: switching to " + (enter ? "fullscreen" : "windowed mode"));
75 | extraQl_SetFullscreen(enter);
76 |
77 | // extraQL may not be running or QL might have ignored the Alt+Enter keypress (when disconnecting), so check again after a timeout
78 | setTimeout(1000, function () {
79 | if (qz_instance.GetCvar("r_fullscreen") != enter)
80 | qz_instance.SendGameCommand("set r_fullscreen " + mode + "; vid_restart");
81 | });
82 | }
83 | }
84 |
85 | function extraQl_SetFullscreen(mode) {
86 | var xhttp = new XMLHttpRequest();
87 | xhttp.timeout = 1000;
88 | xhttp.onload = function () {
89 | if (xhttp.status == 200) {
90 | log("autoExec.js: switched r_fullscreen=" + mode + " through extraQL.exe");
91 | } else
92 | log("autoExec.js: failed to switch r_fullscreen=" + mode + " through extraQL.exe: " + xhttp.responseText);
93 | }
94 | xhttp.onerror = function () { echo("^3extraQL.exe not running:^7 run extraQL.exe to prevent a map-reload when switching fullscreen"); }
95 | xhttp.open("GET", "http://localhost:27963/toggleFullscreen?mode=" + mode, true);
96 | xhttp.send();
97 | }
98 |
99 |
100 | // there is a race condition between QL's bundle.js and the userscripts.
101 | // if window.req was published by bundle.js, we're good to go.
102 | // otherwise add a callback to main_hook_v2, which will be called by bundle.js later
103 | if (window.req)
104 | init();
105 | else {
106 | var oldHook = window.main_hook_v2;
107 | window.main_hook_v2 = function() {
108 | if (typeof oldHook == "function")
109 | oldHook();
110 | init();
111 | }
112 | }
113 | })();
114 |
115 |
--------------------------------------------------------------------------------
/source/scripts/instaBounce.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name InstaBounce: Automate special config with key binds for InstaBounce gametype
3 | // @version 1.1
4 | // @author PredatH0r
5 | // @description InstaBounce is InstaGib + 0-Damage-Bounce-Rockets + Grappling hook.
6 | // @description This script automatically detects this game type and executes the config files
7 | // @description ibounce_on.cfg and ibounce_off.cfg to setup aliases and key binds.
8 | // @description The aliases +hook and +rock allow you to instantly switch and fire weapons.
9 | // @description Your config is restored when you leave an InstaBounce server.
10 | // @enabled 1
11 | // ==/UserScript==
12 |
13 | /*
14 |
15 | Version 1.1
16 | - fixed issues with restoring original config
17 |
18 | Version 1.0
19 | - initial release
20 |
21 | */
22 |
23 | (function () {
24 | // external global variables
25 | var qz_instance = window.qz_instance;
26 | var console = window.console;
27 |
28 | // constants
29 | var CVAR_DisableMsg = "cg_disableInstaBounceBindMsg";
30 | var CVAR_InstaBounce = "cg_instaBounce";
31 | var IB_DISABLED = "-1";
32 | var IB_AUTODETECT = "0";
33 | var IB_ACTIVE = "1";
34 |
35 | // internal variables
36 | var ignoreEvents = false;
37 | var mainMenuTimer;
38 |
39 |
40 | function init() {
41 | restoreNormalConfig(); // in case someone used /quit and the normal config wasn't restored
42 | var postal = window.req("postal");
43 | var channel = postal.channel();
44 | channel.subscribe("cvar.ui_mainmenu", onUiMainMenu); // used to detect in-game vs. menu-mode
45 | channel.subscribe("cvar.cg_spectating", onSpectating); // wait for the player to join so he can see the "print" message
46 | var ib = qz_instance.GetCvar(CVAR_InstaBounce);
47 | var status = ib == IB_AUTODETECT || ib == IB_ACTIVE ? "auto-detect" : "disabled";
48 | echo("^2instaBounce.js installed^7 (" + CVAR_InstaBounce + "=" + ib + ": " + status + ")");
49 | }
50 |
51 | function log(msg) {
52 | console.log(msg);
53 | }
54 |
55 | function echo(msg) {
56 | msg = msg.replace(/\"/g, "'").replace(/[\r\n]+/g, " ");
57 | qz_instance.SendGameCommand("echo \"" + msg + "\"");
58 | }
59 |
60 | function onUiMainMenu(data) {
61 | if (ignoreEvents) return;
62 |
63 | // When changing maps, ui_mainMenu gets set to 1 and back to 0.
64 | // To prevent unnecessary config resetting and console flooding, we use a timeout
65 | if (data.value == "0") {
66 | if (mainMenuTimer)
67 | clearTimeout(mainMenuTimer);
68 | checkFactoryForInstaBounce();
69 | }
70 | else {
71 | mainMenuTimer = setTimeout(restoreNormalConfig, 1000);
72 | }
73 | }
74 |
75 | function onSpectating(data) {
76 | if (ignoreEvents) return;
77 | if (parseInt(data.value) == 0 && qz_instance.GetCvar(CVAR_InstaBounce) == IB_ACTIVE)
78 | showKeyBindMessage();
79 | }
80 |
81 | function restoreNormalConfig() {
82 | mainMenuTimer = undefined;
83 | if (qz_instance.GetCvar(CVAR_InstaBounce) != IB_ACTIVE)
84 | return;
85 | ignoreEvents = true;
86 | qz_instance.SendGameCommand("exec ibounce_off.cfg");
87 | ignoreEvents = false;
88 | qz_instance.SetCvar(CVAR_InstaBounce, "1");
89 | echo("^3instaBounce.js:^7 restored normal config (ibounce_off.cfg)");
90 | }
91 |
92 | function checkFactoryForInstaBounce() {
93 | if (qz_instance.GetCvar(CVAR_InstaBounce) == IB_DISABLED)
94 | return;
95 | qz_instance.SendGameCommand("serverinfo");
96 | qz_instance.SendGameCommand("condump extraql_condump.txt");
97 | setTimeout(function() {
98 | var xhttp = new XMLHttpRequest();
99 | xhttp.timeout = 1000;
100 | xhttp.onload = function() { extraQLCondumpOnLoad(xhttp); }
101 | xhttp.onerror = function() { echo("^3extraQL.exe not running:^7 run extraQL.exe to auto-exec ibounce_on/off.cfg when connecting to (non-)InstaBounce servers."); }
102 | xhttp.open("GET", "http://localhost:27963/condump", true);
103 | xhttp.send();
104 | }, 100);
105 | }
106 |
107 | function extraQLCondumpOnLoad(xhttp) {
108 | var isInstaBounce = false;
109 | if (xhttp.status == 200) {
110 | var condump = xhttp.responseText;
111 | var idx, idx2;
112 | if ((idx = condump.lastIndexOf("g_factoryTitle")) >= 0 && (idx2 = condump.indexOf("\n", idx)) >= 0) {
113 | var factory = condump.substr(idx + 14, idx2 - idx - 14).trim();
114 | if (factory.indexOf("InstaBounce") >= 0)
115 | isInstaBounce = true;
116 | }
117 | else {
118 | log("cound not detect g_factory in condump");
119 | }
120 | }
121 | else
122 | log("^1instaBounce.js:^7 failed to load serverinfo/condump through extraQL.exe: " + xhttp.responseText);
123 |
124 | if (isInstaBounce) {
125 | if (qz_instance.GetCvar(CVAR_InstaBounce) == IB_AUTODETECT) {
126 | qz_instance.SendGameCommand("writeconfig ibounce_off.cfg"); // backup current config
127 | // writeconfig doesn't write immediately, so we have to defer the config changes
128 | setTimeout(function() {
129 | qz_instance.SendGameCommand("exec ibounce_on.cfg");
130 | qz_instance.SetCvar(CVAR_InstaBounce, IB_ACTIVE);
131 | echo("^3instaBounce.js:^7 activated InstaBounce config (ibounce_on.cfg)");
132 | }, 1000);
133 | }
134 | }
135 | else
136 | restoreNormalConfig();
137 | }
138 |
139 | function showKeyBindMessage() {
140 | if (qz_instance.GetCvar(CVAR_DisableMsg) == "1")
141 | return;
142 | var msg = "^3Edit^7 Quake Live//baseq3/^3ibounce_on.cfg^7 to ^5bind +hook^7 and ^5+rock^7 to your preferred keys/buttons and disable this message.";
143 | qz_instance.SendGameCommand("echo \"" + msg + "\"");
144 | qz_instance.SendGameCommand("print \"" + msg + "\"");
145 | msg = "^7Your original config will be restored automatically. You can also force it with ^5exec ibounce_off.cfg^7.";
146 | qz_instance.SendGameCommand("echo \"" + msg + "\"");
147 | }
148 |
149 |
150 | // there is a race condition between QL's bundle.js and the userscripts.
151 | // if window.req was published by bundle.js, we're good to go.
152 | // otherwise add a callback to main_hook_v2, which will be called by bundle.js later
153 | if (window.req)
154 | init();
155 | else {
156 | var oldHook = window.main_hook_v2;
157 | window.main_hook_v2 = function () {
158 | if (typeof oldHook == "function")
159 | oldHook();
160 | init();
161 | }
162 | }
163 | })();
164 |
165 |
--------------------------------------------------------------------------------
/source/scripts/postalDump.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Postal Dump (for Developers): Print all javascript postal messages in the console.
3 | // @version 1.0
4 | // @author PredatH0r
5 | // @description Enable developer console messages with /web_console 1 (restart may be required).
6 | // @description After that you'll see all postal notification about cvar changes and other events.
7 | // @enabled 0
8 | // ==/UserScript==
9 |
10 | /*
11 |
12 | for developers only.
13 | uncomment the last line of this script to get all "postal" notifications logged in your console
14 |
15 | */
16 |
17 | (function () {
18 | // external global variables
19 | var qz_instance = window.qz_instance;
20 | var console = window.console;
21 |
22 | function init() {
23 | var postal = window.req("postal");
24 | var channel = postal.channel();
25 | channel.subscribe("#", onPostalEvent);
26 | echo("^2postalDump.js installed");
27 | }
28 |
29 | function log(msg) {
30 | console.log(msg);
31 | }
32 |
33 | function echo(msg) {
34 | msg = msg.replace(/\"/g, "'").replace(/[\r\n]+/g, " ");
35 | qz_instance.SendGameCommand("echo \"" + msg + "\"");
36 | }
37 |
38 | function onPostalEvent(data, envelope) {
39 | echo("postal data: " + JSON.stringify(data));
40 | echo("postal envelope: " + JSON.stringify(envelope));
41 | }
42 |
43 | // there is a race condition between QL's bundle.js and the userscripts.
44 | // if window.req was published by bundle.js, we're good to go.
45 | // otherwise add a callback to main_hook_v2, which will be called by bundle.js later
46 | if (window.req)
47 | init();
48 | else {
49 | var oldHook = window.main_hook_v2;
50 | window.main_hook_v2 = function() {
51 | if (typeof oldHook == "function")
52 | oldHook();
53 | init();
54 | }
55 | }
56 | })();
57 |
58 |
--------------------------------------------------------------------------------
/source/scripts/steamNick.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name SteamNick: Adds cvars to change your Steam nickname
3 | // @version 1.3
4 | // @author PredatH0r
5 | // @description /steamnick sets your Steam nickname to
6 | // @description /sn_clan + /sn_name + /sn_suffix are combined into /steamnick
7 | // @description Only the sn_* variables will change your Steam Nick when QL is loaded.
8 | // @description You can use /steamnick in combination with AutoExec's gamestart.cfg/gameend.cfg.
9 | // @enabled 1
10 | // ==/UserScript==
11 |
12 | /*
13 |
14 | Version 1.3
15 | - added /sn_suffix
16 |
17 | Version 1.2
18 | - improved error handling when name change fails
19 |
20 | version 1.1
21 | - added /sn_name and /sn_clan
22 |
23 | Version 1.0
24 | - first user script designed to work with Steam exclusive version of Quake Live
25 |
26 | */
27 |
28 | (function () {
29 | // external global variables
30 | var qz_instance = window.qz_instance;
31 | var console = window.console;
32 |
33 | // constants
34 | var STEAMNICK_CVAR = "steamnick";
35 | var NAME_CVAR = "sn_name";
36 | var CLAN_PREFIX_CVAR = "sn_clan";
37 | var CLAN_SUFFIX_CVAR = "sn_suffix";
38 |
39 | function init() {
40 | var steamName = qz_instance.GetCvar("name");
41 |
42 | var clanPrefix = qz_instance.GetCvar(CLAN_PREFIX_CVAR);
43 | if (!clanPrefix) {
44 | // make sure the CVAR exists
45 | qz_instance.SetCvar(CLAN_PREFIX_CVAR, "");
46 | clanPrefix = "";
47 | }
48 |
49 | var clanSuffix = qz_instance.GetCvar(CLAN_SUFFIX_CVAR);
50 | if (!clanSuffix) {
51 | // make sure the CVAR exists
52 | qz_instance.SetCvar(CLAN_SUFFIX_CVAR, "");
53 | clanSuffix = "";
54 | }
55 |
56 | var nickname = qz_instance.GetCvar(NAME_CVAR);
57 | if (!nickname) {
58 | // make sure the CVAR exists
59 | qz_instance.SetCvar(NAME_CVAR, steamName);
60 | nickname = "";
61 | }
62 |
63 |
64 | // if any of the name/tag cvars are set, then modify the steam name accordingly
65 | if (clanPrefix || clanSuffix || nickname) {
66 | var steamnick = clanPrefix + nickname + clanSuffix;
67 | qz_instance.SetCvar(STEAMNICK_CVAR, steamnick);
68 | onSteamNickCvarChanged({ name: STEAMNICK_CVAR, value: steamnick });
69 | }
70 | else
71 | qz_instance.SetCvar(STEAMNICK_CVAR, steamName);
72 |
73 |
74 | var postal = window.req("postal");
75 | var channel = postal.channel();
76 | channel.subscribe("cvar." + STEAMNICK_CVAR, onSteamNickCvarChanged);
77 | channel.subscribe("cvar." + CLAN_PREFIX_CVAR, onSteamNickCvarChanged);
78 | channel.subscribe("cvar." + CLAN_SUFFIX_CVAR, onSteamNickCvarChanged);
79 | channel.subscribe("cvar." + NAME_CVAR, onSteamNickCvarChanged);
80 | echo("^2steamNick.js installed");
81 | }
82 |
83 | function log(msg) {
84 | console.log(msg);
85 | }
86 |
87 | function echo(msg) {
88 | msg = msg.replace(/\"/g, "'").replace(/[\r\n]+/g, " ");
89 | qz_instance.SendGameCommand("echo \"" + msg + "\"");
90 | }
91 |
92 | function onSteamNickCvarChanged(data) {
93 | // German keyboard cannot enter ^ (it's the console key), so allow \ for colors too. Updating the cvar will call this function again.
94 | var value = data.value.replace(/\\/g, "^");
95 | if (value != data.value) {
96 | qz_instance.SetCvar(data.name, value);
97 | return;
98 | }
99 |
100 | // if any of the sn_* cvars was changed, combine them into "steamnick", which will cause another call to this function
101 | if (data.name != STEAMNICK_CVAR) {
102 | var newNick = qz_instance.GetCvar(CLAN_PREFIX_CVAR) + qz_instance.GetCvar(NAME_CVAR) + qz_instance.GetCvar(CLAN_SUFFIX_CVAR);
103 | if (newNick)
104 | qz_instance.SetCvar(STEAMNICK_CVAR, newNick);
105 | return;
106 | }
107 |
108 | // don't allow empty steam nicknames
109 | if (!value || value == qz_instance.GetCvar("name"))
110 | return;
111 |
112 | // call the extraQL.exe servlet which changes the Steam nickname through a steam_api.dll call
113 | var xhttp = new XMLHttpRequest();
114 | xhttp.timeout = 10000;
115 | xhttp.onload = function () {
116 | if (xhttp.status == 200)
117 | echo("^3/" + data.name + " changed successfully.^7");
118 | else {
119 | echo("^1/" + data.name + " failed: ^7" + xhttp.responseText);
120 | qz_instance.SetCvar(STEAMNICK_CVAR, qz_instance.GetCvar("name"));
121 | }
122 | }
123 | xhttp.onerror = function() {
124 | echo("^1/" + STEAMNICK_CVAR + " timed out. ^7Please make sure extraQL.exe 2.0 (or newer) is running on your PC.");
125 | qz_instance.SetCvar(STEAMNICK_CVAR, qz_instance.GetCvar("name"));
126 | }
127 | xhttp.open("GET", "http://localhost:27963/steamnick?name=" + encodeURIComponent(value), true);
128 | xhttp.send();
129 | }
130 |
131 |
132 | // there is a race condition between QL's bundle.js and the userscripts.
133 | // if window.req was published by bundle.js, we're good to go.
134 | // otherwise add a callback to main_hook_v2, which will be called by bundle.js later
135 | if (window.req)
136 | init();
137 | else {
138 | var oldHook = window.main_hook_v2;
139 | window.main_hook_v2 = function() {
140 | if (typeof oldHook == "function")
141 | oldHook();
142 | init();
143 | }
144 | }
145 | })();
146 |
147 |
--------------------------------------------------------------------------------
/source/steam_api.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/steam_api.dll
--------------------------------------------------------------------------------
/source/translation.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/translation.xlsx
--------------------------------------------------------------------------------
/workshop/extraQL.vdf:
--------------------------------------------------------------------------------
1 | "workshopitem"
2 | {
3 | "appid" "282440"
4 | "publishedfileid" "539252269"
5 | "contentfolder" "d:\sources\extraQL\workshop\content"
6 | "previewfile" "d:\sources\extraQL\workshop\extraQL1.png"
7 | "visibility" "0"
8 | "title" "extraQL userscripts"
9 | "changenote" "https://github.com/PredatH0r/extraQL/releases/tag/v2.24"
10 | }
11 |
--------------------------------------------------------------------------------
/workshop/extraQL1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/workshop/extraQL1.png
--------------------------------------------------------------------------------
/workshop/extraQL2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/workshop/extraQL2.png
--------------------------------------------------------------------------------
/workshop/extraQL3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/workshop/extraQL3.png
--------------------------------------------------------------------------------