-
-
-
+ The trajectory of this ship is unchanging.
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
+
+
+
+
+
+
+
+
diff --git a/html/js/art.js b/html/js/art.js
new file mode 100644
index 0000000..9346d17
--- /dev/null
+++ b/html/js/art.js
@@ -0,0 +1,369 @@
+const ARTS = [
+ {
+ lines: [
+ ' ____________________________________________________________________________',
+ ' ___/\\/\\/\\/\\____/\\/\\________/\\/\\______/\\/\\________________________/\\/\\_______',
+ ' _/\\/\\__________/\\/\\________________/\\/\\/\\/\\/\\__________/\\/\\/\\/\\__/\\/\\_______',
+ ' _/\\/\\/\\/\\/\\____/\\/\\/\\/\\____/\\/\\______/\\/\\____________/\\/\\________/\\/\\/\\/\\___',
+ ' _/\\/\\____/\\/\\__/\\/\\__/\\/\\__/\\/\\______/\\/\\______/\\/\\__/\\/\\________/\\/\\__/\\/\\_',
+ ' ___/\\/\\/\\/\\____/\\/\\/\\/\\____/\\/\\/\\____/\\/\\/\\____/\\/\\____/\\/\\/\\/\\__/\\/\\__/\\/\\_',
+ '____________________________________________________________________________'
+ ]
+ },
+ {
+ lines: [
+ ' @@@@@@ @@@@@@@ @@@ @@@@@@@ @@@@@@@ @@@ @@@ ',
+ ' @@@@@@@ @@@@@@@@ @@@ @@@@@@@ @@@@@@@@ @@@ @@@ ',
+ '!@@ @@! @@@ @@! @@! @@@ @@! @@@ ',
+ '!@! !@ @!@ !@! !@! !@! !@! @!@ ',
+ '!!@@!@! @!@!@!@ !!@ @!! !@! @!@!@!@! ',
+ '@!!@!!!! !!!@!!!! !!! !!! !!! !!!@!!!! ',
+ '!:! !:! !!: !!! !!: !!: :!! !!: !!! ',
+ ':!: !:! :!: !:! :!: :!: :!: :!: :!: !:! ',
+ ':::: ::: :: ::: :: :: ::: ::: ::: :: ::: ',
+ ' :: : : : : :: : : : :: :: : : :: '
+ ]
+ },
+ {
+ lines: [
+ ' _____ _____ _____ _____ _____ _____ _____ ',
+ '|| ||| ||| ||| ||| ||| ||| ||',
+ '|| 6 ||| B ||| I ||| T ||| . ||| C ||| H ||',
+ '||_____|||_____|||_____|||_____|||_____|||_____|||_____||',
+ '|/_____\\|/_____\\|/_____\\|/_____\\|/_____\\|/_____\\|/_____\\|'
+ ]
+ },
+ {
+ lines: [
+ ' o__ __o o o o o ',
+ ' /v v\\ <|> _<|>_ <|> <|> ',
+ ' /> <\\ / > < > / > ',
+ '
- The trajectory of this ship is unchanging.
- - -
+
+ ๐ก here
+-
+
- ๐ Goat +
- ๐ก Slamp +
- ๐ช Wiki +
- ๐ q3cfg +
+
+ โจ there
+ +
+
+ ๐ญ everywhere
+-
+
- ยป Aus Liebe +
- ยป Catcalls of Zurich +
- ยป Cannamatch +
- ยป cramp +
- ยป JAR +
- ยป LeLAN +
+
+
+ ๐พ qlpycon
+
+
+
+
+ + ++
QLPyCon
+Quake Live Python Console
+Terminal-based client written in python for monitoring and controlling Quake Live servers via ZMQ
+ + + +- Real-time server monitoring with live feed of game events
+- Server info display and full control via rcon
+- Autocomplete cvars and commands with fuzzy matching
+- Intelligent argument suggestions for 25+ commands
+- Quake color code support (^0-^7)
+- Command history & JSON event capture
++
License: WTFPL
++
pipe2bash (stable):
+
+
+ curl -sSL https://git.6bit.ch/xbl/qlpycon/raw/branch/main/install.sh | bash
+git clone (edge):
+
+
+ git clone https://git.6bit.ch/xbl/qlpycon.git
+cd qlpycon
+chmod u+x install.sh
+./install.sh
+
+
+
+ ๐จ Things
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
๐ Snek (arrow keys required)
+ +0 ' + p + '
'; }).join('');
+ var blueRows = cfg.blueTeam.map(function(p){ return '0 ' + p + '
'; }).join('');
+ var label = cfg.serverLabel.split(' ');
+ var bracket = label[0] || '';
+ var cyan = label[1] || '';
+ var yellow = label.slice(2).join(' ') || '';
+
+ var css = [
+ '*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}',
+ 'html,body{background:transparent;color:#ccc;font-family:"Courier New",Courier,monospace;font-size:12.5px;line-height:1.5;overflow:hidden}',
+ '.wrap{border:1px solid #333}',
+ '.header{padding:6px 12px 4px}',
+ '.titlebar{color:#ccc;margin-bottom:6px}',
+ '.infoline{display:grid;grid-template-columns:auto auto auto auto;margin-bottom:1px}',
+ '.teams{padding:8px 12px 4px;display:flex}',
+ '.team{width:50%}',
+ '.team-hdr{margin-bottom:3px}',
+ '.player{color:#fff;padding-left:2px}',
+ '.spectators{padding:4px 12px 2px;color:#ff0;font-weight:bold}',
+ '.divider{border:none;border-top:1px solid #444;margin:6px 0 0;display:block}',
+ '.feed-wrap{height:256px;overflow:hidden;padding:4px 12px;position:relative}',
+ '.fade{position:absolute;top:4px;left:0;right:0;height:30px;background:linear-gradient(to bottom,#0c0c0c,transparent);pointer-events:none;z-index:2}',
+ '.feed{display:flex;flex-direction:column}',
+ '.msg{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:0;transition:opacity .25s ease;line-height:1.6;display:block}',
+ '.msg.on{opacity:1}',
+ '.cmdline{border-top:1px solid #333;padding:4px 12px;display:flex;align-items:center;gap:4px;color:#ccc}',
+ '.cursor{display:inline-block;width:8px;height:14px;background:#ccc;vertical-align:middle;animation:blink 1s step-end infinite}',
+ '@keyframes blink{0%,100%{opacity:1}50%{opacity:0}}',
+ '.cyan{color:#fff}.yellow{color:#0cc}.warmup{color:#f00}',
+ '.bold{color:#fff;font-weight:bold}',
+ '.rt{color:#f00;text-decoration:underline;font-weight:bold}',
+ '.bt{color:#09f;text-decoration:underline;font-weight:bold}',
+ '.score{color:#fff;font-weight:bold;margin-right:6px}',
+ '.ts{color:#ccc}.cr{color:#f00}.cb{color:#09f}.cp{color:#fff;font-weight:bold}',
+ '.cmt{color:#f0f;font-weight:bold}',
+ '.cst{color:#0c0;font-weight:bold}.css{color:#0f0}',
+ '.plain{color:#ccc}',
+ '.label{color:#ff0}',
+ ].join('');
+
+ var html = [
+ '',
+ '',
+ '',
+ '',
+ '
', + '
',
+ ].join('');
+
+ /* inner script as a real function โ no string escaping needed */
+ function iframeScript(cfg) {
+ var feed = document.getElementById('feed');
+ var timeEl = document.getElementById('qtime');
+ var rsEl = document.getElementById('rs');
+ var bsEl = document.getElementById('bs');
+ var lines = [], elapsed = 0, scores = { red: 0, blue: 0 };
+
+ function rand(a) { return a[Math.floor(Math.random() * a.length)]; }
+ function randInt(a,b){ return a + Math.floor(Math.random() * (b - a + 1)); }
+ function sp(c, t) { return '' + t + ''; }
+ function pad(n) { return n < 10 ? '0' + n : '' + n; }
+ function ts() {
+ var b = 33*60 + 34 + elapsed;
+ return '[' + pad(Math.floor(b/3600)) + ':' + pad(Math.floor((b%3600)/60)) + ':' + pad(b%60) + ']';
+ }
+
+ function kill() {
+ var rk = Math.random() < 0.5;
+ var k = rand(rk ? cfg.redTeam : cfg.blueTeam);
+ var v = rand(rk ? cfg.blueTeam : cfg.redTeam);
+ var w = rand(cfg.weapons);
+ var hp = randInt(25, 200);
+ if (Math.random() < 0.12) { if (rk) scores.red++; else scores.blue++; }
+ return sp('ts',ts()) + ' ' +
+ sp(rk?'cr':'cb', rk?'(RED)':'(BLUE)') + sp('cp', k) +
+ sp('plain', ' fragged ') +
+ sp(rk?'cb':'cr', rk?'(BLUE)':'(RED)') + sp('cp', v) +
+ sp('plain', ' with the ') +
+ (function(w){
+ var weaponColors = {
+ 'Rocket Launcher':'#f33',
+ 'Plasma Gun':'#f0f',
+ 'Railgun':'#0f0',
+ 'Lightning Gun':'#ff0',
+ 'Shotgun':'#f60'
+ };
+ return '' + w + '';
+ })(w) + ' ' +
+ sp('chp', '(' + hp + ' HP)');
+ }
+
+ function medal() {
+ var r = Math.random() < 0.5;
+ return sp('ts',ts()) + ' ' +
+ sp('cmt', '[MEDAL]') + ' ' +
+ sp(r?'cr':'cb', r?'(RED)':'(BLUE)') +
+ sp('cp', rand(r ? cfg.redTeam : cfg.blueTeam)) +
+ sp('plain', ' got ') +
+ (function(m){
+ var medalColors = {
+ 'EXCELLENT':'#ff0',
+ 'MIDAIR':'#0f0',
+ 'IMPRESSIVE':'#0cc',
+ 'REVENGE':'#f00'
+ };
+ return '' + m + '';
+ })(rand(cfg.medals));
+ }
+
+ function say() {
+ var r = Math.random() < 0.5;
+ return sp('ts',ts()) + ' ' +
+ sp('cst', '[SAY]') + ' ' +
+ sp(r?'cr':'cb', r?'(RED)':'(BLUE)') +
+ sp('cp', rand(r ? cfg.redTeam : cfg.blueTeam)) +
+ ': ' + sp('css', rand(cfg.chatMessages));
+ }
+
+ function addLine(h) {
+ var d = document.createElement('div');
+ d.className = 'msg';
+ d.innerHTML = h;
+ feed.appendChild(d);
+ lines.push(d);
+ requestAnimationFrame(function(){ d.classList.add('on'); });
+ if (lines.length > cfg.maxLines) lines.shift().remove();
+ rsEl.textContent = scores.red;
+ bsEl.textContent = scores.blue;
+ }
+
+ function schedule() {
+ setTimeout(function() {
+ var r = Math.random();
+ if (r < cfg.pKill) addLine(kill());
+ else if (r < cfg.pKill + cfg.pMedal) addLine(medal());
+ else addLine(say());
+ schedule();
+ }, randInt(cfg.minDelay, cfg.maxDelay));
+ }
+
+ addLine(sp('plain', '*** '));
+ addLine(sp('plain', '*** QL PyCon Version 0.8.1 starting ***'));
+ addLine(sp('plain', 'Stats stream connected - ready for game events'));
+
+ setInterval(function() {
+ elapsed++;
+ var m = Math.floor(elapsed / 60), s = elapsed % 60;
+ timeEl.textContent = m + ':' + (s < 10 ? '0' : '') + s;
+ }, 1000);
+
+ setTimeout(schedule, 900);
+ }
+
+ var scriptBody = '(' + iframeScript.toString() + ')(' + JSON.stringify(cfg) + ')';
+
+ return html + '',
+ '',
+ '',
+ '',
+ '
',
+ '',
+ '
',
+ '',
+ '
',
+ '0RED TEAM
',
+ redRows,
+ '',
+ '
',
+ '0BLUE TEAM
',
+ blueRows,
+ 'Spectators: ' + cfg.spectators.map(colorizeSpectator).join(' ') + '
',
+ '', + '
',
+ '',
+ '',
+ '
',
+ '$
',
+ '"; +}); + +document.getElementById("qlpyconbanner").innerHTML = out; diff --git a/html/js/snek.js b/html/js/snek.js new file mode 100644 index 0000000..d829d30 --- /dev/null +++ b/html/js/snek.js @@ -0,0 +1,158 @@ +var snekStarted = false; +document.querySelector('#snek-details').addEventListener('toggle', function(e) { + if (e.target.open && !snekStarted) { + snekStarted = true; + +var canvas = document.getElementById('game'); +var context = canvas.getContext('2d'); + +var grid = 8; +var count = 8; + +var snake = { + x: 160, + y: 160, + + // snake velocity. moves one grid length every frame in either the x or y direction + dx: grid, + dy: 0, + + // keep track of all grids the snake body occupies + cells: [], + + // length of the snake. grows when eating an apple + maxCells: 4 +}; + +var cols = Math.floor(canvas.width / grid); +var rows = Math.floor(canvas.height / grid); +var apple = { + x: getRandomInt(0, cols) * grid, + y: getRandomInt(0, rows) * grid, +}; + +// get random whole numbers in a specific range +// @see https://stackoverflow.com/a/1527820/2124254 +function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min)) + min; +} + +// game loop +function loop() { + requestAnimationFrame(loop); + + // slow game loop to 15 fps instead of 60 (60/15 = 4) + if (++count < 11) { + return; + } + + count = 0; + context.clearRect(0,0,canvas.width,canvas.height); + + context.font = '9px sans-serif'; + context.fillStyle = '#696969'; + context.fillText(snake.maxCells, canvas.width - 20, 14); + + // move snake by it's velocity + snake.x += snake.dx; + snake.y += snake.dy; + + // wrap snake position + if (snake.x < 0) { + snake.x = (cols - 1) * grid; + } + else if (snake.x >= canvas.width) { + snake.x = 0; + } + if (snake.y < 0) { + snake.y = (rows - 1) * grid; + } + else if (snake.y >= canvas.height) { + snake.y = 0; + } + + // keep track of where snake has been. front of the array is always the head + snake.cells.unshift({x: snake.x, y: snake.y}); + + // remove cells as we move away from them + if (snake.cells.length > snake.maxCells) { + snake.cells.pop(); + } + + // draw apple + context.fillStyle = 'cyan'; + context.fillRect(apple.x, apple.y, grid-1, grid-1); + + // draw snake one cell at a time + context.fillStyle = 'magenta'; + snake.cells.forEach(function(cell, index) { + + // drawing 1 px smaller than the grid creates a grid effect in the snake body so you can see how long it is + context.fillRect(cell.x, cell.y, grid-1, grid-1); + + // snake ate apple + if (cell.x === apple.x && cell.y === apple.y) { + snake.maxCells++; + + // canvas is 400x400 which is 25x25 grids + apple.x = getRandomInt(0, cols) * grid; + apple.y = getRandomInt(0, rows) * grid; + } + + // check collision with all cells after this one (modified bubble sort) + for (var i = index + 1; i < snake.cells.length; i++) { + + // snake occupies same space as a body part. reset game + if (cell.x === snake.cells[i].x && cell.y === snake.cells[i].y) { + snake.x = 160; + snake.y = 160; + snake.cells = []; + snake.maxCells = 4; + snake.dx = grid; + snake.dy = 0; + + apple.x = getRandomInt(0, 25) * grid; + apple.y = getRandomInt(0, 25) * grid; + } + } + }); +} + +// listen to keyboard events to move the snake +document.addEventListener('keydown', function(e) { + if (document.getElementById('snek-details').open) { + if ([37, 38, 39, 40].indexOf(e.which) !== -1) { + e.preventDefault(); + } + } + // prevent snake from backtracking on itself by checking that it's + // not already moving on the same axis (pressing left while moving + // left won't do anything, and pressing right while moving left + // shouldn't let you collide with your own body) + + // left arrow key + if (e.which === 37 && snake.dx === 0) { + snake.dx = -grid; + snake.dy = 0; + } + // up arrow key + else if (e.which === 38 && snake.dy === 0) { + snake.dy = -grid; + snake.dx = 0; + } + // right arrow key + else if (e.which === 39 && snake.dx === 0) { + snake.dx = grid; + snake.dy = 0; + } + // down arrow key + else if (e.which === 40 && snake.dy === 0) { + snake.dy = grid; + snake.dx = 0; + } +}); + +// start the game +requestAnimationFrame(loop); +} +}); diff --git a/html/js/sparks.js b/html/js/sparks.js new file mode 100644 index 0000000..16f7d15 --- /dev/null +++ b/html/js/sparks.js @@ -0,0 +1,28 @@ +$(function() { + var body = $('#starshine'), + template = $('.template.shine'), + stars = 500, + sparkle = 50; + + var size = 'small'; + var createStar = function() { + template.clone().removeAttr('id').css({ + top: (Math.random() * 100) + '%', + left: (Math.random() * 100) + '%', + webkitAnimationDelay: (Math.random() * sparkle) + 's', + mozAnimationDelay: (Math.random() * sparkle) + 's' + }).addClass(size).appendTo(body); + }; + + for(var i = 0; i < stars; i++) { + if(i % 2 === 0) { + size = 'small'; + } else if(i % 3 === 0) { + size = 'medium'; + } else { + size = 'large'; + } + + createStar(); + } +}); diff --git a/html/js/stars.js b/html/js/stars.js new file mode 100644 index 0000000..4efe86d --- /dev/null +++ b/html/js/stars.js @@ -0,0 +1,85 @@ +/* stars.js โ static starry background for 6bit.ch + Requires a

















