Last active
February 20, 2025 03:07
-
-
Save nabbynz/18064d818c5d9e39e65d7682e1d826dd to your computer and use it in GitHub Desktop.
TagPro Highlight Flag Holder + Follow NF Flag in Spec
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name TagPro Highlight Flag Holder + Follow NF Flag in Spec | |
// @description Adds a circle to the flag holder. Follows the NF Flag Holder when spectating | |
// @version 1.3.0 | |
// @match *://*.koalabeast.com/game | |
// @match *://*.koalabeast.com/game?* | |
// @updateURL https://gist.github.com/nabbynz/18064d818c5d9e39e65d7682e1d826dd/raw/TagPro_Highlight_Flag_Holder.user.js | |
// @downloadURL https://gist.github.com/nabbynz/18064d818c5d9e39e65d7682e1d826dd/raw/TagPro_Highlight_Flag_Holder.user.js | |
// @grant none | |
// @author nabby | |
// ==/UserScript== | |
console.log('START: ' + GM_info.script.name + ' (v' + GM_info.script.version + ' by ' + GM_info.script.author + ')'); | |
'use-strict'; | |
/* globals tagpro, tagproConfig, PIXI */ | |
/* eslint-disable no-multi-spaces */ | |
/* eslint-disable dot-notation */ | |
// Options... | |
let USE_GLOW_CIRCLES = true; // apply the circle highlight | |
let ADD_CIRCLE_TO_FLAG = false; // true = add circle to flag; false = add circle to ball | |
let USE_FOLLOW_YELLOW_FLAG_IN_SPEC = true; // follow the flag holder in NF | |
tagpro.ready(function() { | |
let tr = tagpro.renderer; | |
let flagGlowTexture; | |
let createFlagGlowTexture = function() { | |
const canvasSize = 64; | |
const canvasCenter = canvasSize / 2; | |
const glowOpacity = ADD_CIRCLE_TO_FLAG ? 0.8 : 1; | |
const glowBlur = ADD_CIRCLE_TO_FLAG ? 2 : 0; | |
const glowRadius = ADD_CIRCLE_TO_FLAG ? 17 : 12; | |
const holeRadius = ADD_CIRCLE_TO_FLAG ? 13 : 8; | |
const holeOpacity = ADD_CIRCLE_TO_FLAG ? 1 : 1; | |
const holeBlur = ADD_CIRCLE_TO_FLAG ? 1 : 0; | |
let canvas = createCanvas(canvasSize, canvasSize); | |
let ctx = canvas.getContext('2d'); | |
ctx.fillStyle = '#ffffff'; | |
ctx.globalAlpha = glowOpacity; | |
ctx.filter = 'blur(' + glowBlur + 'px)'; | |
ctx.beginPath(); | |
ctx.arc(canvasCenter, canvasCenter, glowRadius, 0, Math.PI * 2); | |
ctx.fill(); | |
if (holeRadius > 0 && holeOpacity > 0) { | |
ctx.globalCompositeOperation = 'destination-out'; | |
ctx.globalAlpha = holeOpacity; | |
ctx.filter = 'blur(' + holeBlur + 'px)'; | |
ctx.beginPath(); | |
ctx.arc(canvasCenter, canvasCenter, holeRadius, 0, Math.PI * 2); | |
ctx.fill(); | |
} | |
flagGlowTexture = PIXI.Texture.from(canvas); | |
}; | |
createFlagGlowTexture(); | |
const glowColors = { | |
'redflag': 0xffd100, | |
'blueflag': 0xffd100, | |
'yellowflag': 0xffd100, | |
'redpotato': 0xffd100, | |
'bluepotato': 0xffd100, | |
'yellowpotato': 0xffff00, | |
'redclutch': 0xff44ff, | |
'blueclutch': 0xff44ff, | |
'yellowclutch': 0xff44ff, | |
'none': 0xffffff, | |
'glowFlag': 0xff00ff, | |
}; | |
let addFlagGlowSprite = function(player) { | |
if (player.sprites.glowFlag) { | |
player.sprites.glowFlag.destroy(); | |
} | |
player.sprites.glowFlag = new PIXI.Sprite(flagGlowTexture); | |
player.sprites.glowFlag.name = 'glowFlag'; | |
player.sprites.glowFlag.anchor.set(0.5, 0.5); | |
player.sprites.glowFlag.name = player.sprites.flag ? player.sprites.flag.name : 'glowFlag'; | |
player.sprites.glowFlag.tint = glowColors[player.sprites.glowFlag.name]; | |
player.sprites.glowFlag.visible = false; | |
if (ADD_CIRCLE_TO_FLAG) { | |
player.sprites.glowFlag.position.set(33, -12); // behind flag | |
player.sprites.flagLayer.addChild(player.sprites.glowFlag); | |
} else { | |
player.sprites.glowFlag.position.set(20, 20); // ball centre | |
player.sprites.ball.addChild(player.sprites.glowFlag); | |
} | |
}; | |
let currentSpecPlayerId = tagpro.playerId; | |
let flagTypes = { yellow: [], red: [], blue: [] }; | |
getMapType = function() { | |
if (!tagpro.map || !tagpro.map[0].length) { | |
setTimeout(getMapType, 20); | |
return; | |
} | |
for (let x = 0; x < tagpro.map.length; x++) { | |
for (let y = 0; y < tagpro.map[0].length; y++) { | |
const baseTileId = Math.floor(tagpro.map[x][y]); | |
if (baseTileId === 3) { | |
flagTypes.red.push({ x: x, y: y }); | |
} else if (baseTileId === 4) { | |
flagTypes.blue.push({ x: x, y: y }); | |
} else if (baseTileId === 16) { | |
flagTypes.yellow.push({ x: x, y: y }); | |
} | |
} | |
} | |
}; | |
getMapType(); // for auto-follow yellow flag | |
let uPF = tr.updatePlayerFlag; | |
tr.updatePlayerFlag = function(player) { | |
uPF(player); | |
if (USE_GLOW_CIRCLES) { | |
if (!player.sprites.glowFlag || player.sprites.flag && (player.sprites.glowFlag.name !== player.sprites.flag.name)) { | |
addFlagGlowSprite(player); | |
} | |
player.sprites.glowFlag.visible = !!player.flag; | |
} | |
// this is to always follow the yellow flag carrier in spec... | |
if (USE_FOLLOW_YELLOW_FLAG_IN_SPEC && tagpro.spectator && !tagpro.viewport.centerLock && player.flag === 3 && player.id !== currentSpecPlayerId) { | |
tagpro.viewport.followPlayer = true; | |
if (player.team === 1) { | |
if (tagproConfig.replay) { | |
tagpro.replayActions.redflagcarrier(true); | |
} else { | |
tagpro.socket.emit('redflagcarrier'); | |
} | |
} else if (player.team === 2) { | |
if (tagproConfig.replay) { | |
tagpro.replayActions.blueflagcarrier(false); | |
} else { | |
tagpro.socket.emit('blueflagcarrier'); | |
} | |
} | |
currentSpecPlayerId = player.id; | |
} | |
}; | |
if (USE_FOLLOW_YELLOW_FLAG_IN_SPEC && (tagpro.spectator || tagproConfig.replay)) { | |
document.addEventListener('keydown', (e) => { | |
if (e.which === 70) { // uses the 'f' key to enable/disable auto-follow (TODO: add visual indicator) | |
if (USE_FOLLOW_YELLOW_FLAG_IN_SPEC) { | |
USE_FOLLOW_YELLOW_FLAG_IN_SPEC = false; | |
} else { | |
USE_FOLLOW_YELLOW_FLAG_IN_SPEC = true; | |
if (tagpro.spectator || tagproConfig.replay) { // in case we moved from spec to playing | |
for (let playerId in tagpro.players) { | |
if (tagpro.players[playerId].flag === 3) { | |
tr.updatePlayerFlag(tagpro.players[playerId]); | |
break; | |
} | |
} | |
} | |
} | |
} | |
}); | |
let lastScoreRed, lastScoreBlue; | |
if (flagTypes.yellow.length === 1 && !flagTypes.red.length && !flagTypes.blue.length) { // single yellow flag only | |
tagpro.socket.on('score', function(data) { // sent at the start of the match (initialize), a cap being scored (move camera), or when a spectator joins (ignore) | |
if (lastScoreRed === undefined || lastScoreBlue === undefined || !USE_FOLLOW_YELLOW_FLAG_IN_SPEC) { // initialize | |
lastScoreRed = data.r; | |
lastScoreBlue = data.b; | |
return; | |
} | |
let centerCamera = true; | |
if (lastScoreRed !== data.r || lastScoreBlue !== data.b) { // cap | |
setTimeout(() => { // player.flag (and tagpro.state at game end) sometimes hasn't been set yet - need a delay | |
if (tagpro.state !== 2) { | |
currentSpecPlayerId = null; | |
for (let playerId in tagpro.players) { | |
if (tagpro.players[playerId].flag === 3) { | |
tr.updatePlayerFlag(tagpro.players[playerId]); | |
centerCamera = false; | |
break; | |
} | |
} | |
if (centerCamera) { | |
setTimeout(() => { | |
for (let playerId in tagpro.players) { // check again for grabs within 500ms | |
if (tagpro.players[playerId].flag === 3) { | |
tr.updatePlayerFlag(tagpro.players[playerId]); | |
centerCamera = false; | |
break; | |
} | |
} | |
if (centerCamera) { | |
tagpro.viewport.followPlayer = false; | |
tr.centerCameraPosition(0); | |
} | |
}, 500); | |
} | |
lastScoreRed = data.r; | |
lastScoreBlue = data.b; | |
} | |
}, 100) | |
} | |
}); | |
} | |
} | |
}); | |
let createCanvas = function(width, height, forceDOM=false) { | |
if (typeof OffscreenCanvas !== 'undefined' && !forceDOM) { | |
return new OffscreenCanvas(width, height); | |
} else { | |
let canvas = document.createElement('canvas'); | |
canvas.width = width; | |
canvas.height = height; | |
return canvas; | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment