Skip to content

Instantly share code, notes, and snippets.

@chrahunt
Created January 22, 2015 18:22
Show Gist options
  • Save chrahunt/954b0a4510df9fe71698 to your computer and use it in GitHub Desktop.
Save chrahunt/954b0a4510df9fe71698 to your computer and use it in GitHub Desktop.
Show arrows indicating current velocity.
// ==UserScript==
// @name Velocity Arrows
// @version 0.1.0
// @description Shows an arrow indicating the player's current velocity. Example of a graphics object drawn static relative to the player.
// @include http://tagpro-*.koalabeast.com:*
// @include http://tangent.jukejuice.com:*
// @include http://*.newcompte.fr:*
// @author snaps
// ==/UserScript==
function scriptContent() {
var Point = function(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.mul = function(f) {
return new Point(this.x * f, this.y * f);
}
Point.prototype.add = function(p) {
if (typeof p == "number")
return new Point(this.x + p, this.y + p);
return new Point(this.x + p.x, this.y + p.y);
}
Point.prototype.dot = function(p) {
return (this.x * p.x + this.y * p.y);
}
Point.prototype.sub = function(p) {
if (typeof p == "number")
return new Point(this.x - p, this.y - p);
return new Point(this.x - p.x, this.y - p.y);
}
Point.prototype.len = function() {
return this.dist(new Point(0, 0));
}
Point.prototype.dist = function(p) {
var diff = this.sub(p);
return Math.sqrt(diff.dot(diff));
}
Point.prototype.normalize = function() {
var n = this.dist(new Point(0, 0));
if (n > 0) return this.div(n);
return new Point(0, 0);
}
Point.prototype.div = function(f) {
return new Point(this.x / f, this.y / f);
}
var DrawUtils = function() {
this.init();
};
// Initialize drawing functions.
DrawUtils.prototype.init = function() {
if (typeof tagpro.renderer == "undefined") {
console.log("Can't handle old canvas!");
return;
}
// Store current player object.
this.self = tagpro.players[tagpro.playerId];
// Store items to be drawn.
this.vectors = {};
// Add vectors container to player sprites object, which is used for holding information about
this.self.sprites.vectors = new PIXI.Graphics();
// sprite is the DisplayObjectContainer that holds items displayed in static positions relative to the character.
this.self.sprite.addChild(this.self.sprites.vectors);
// Center vectors on player (the default origin, (0, 0) is the top-left corner of the player sprite.
this.self.sprites.vectors.position.x = 20;
this.self.sprites.vectors.position.y = 20;
}
DrawUtils.prototype.hideVector = function(name) {
this.vectors[name].container.visible = false;
}
DrawUtils.prototype.showVector = function(name) {
this.vectors[name].container.visible = true;
}
/**
* Adds a vector to be drawn over the current player.
* @param {string} name - The name used to refer to the vector.
* @param {number} [color=0x000000] - The color used when drawing the
* vector.
*/
DrawUtils.prototype.addVector = function(name, color) {
var vector = {
name: name,
container: new PIXI.Graphics(),
color: color || 0x000000
}
this.vectors[name] = vector;
this.self.sprites.vectors.addChild(vector.container);
}
/**
* Updates the vector identified with `name` with the values from
* point `p`.
* @param {string} name - The name of the vector to update.
* @param {Point} p - The point to use to update the vector.
*/
DrawUtils.prototype.updateVector = function(name, p) {
this.vectors[name].x = p.x;
this.vectors[name].y = p.y;
this._drawVector(this.vectors[name]);
}
/**
* Represents a 2d vector emanating from the center of the player,
* along with attributes for drawing.
* @typedef VectorInfo
* @type {object}
* @property {string} name - An identifier for the vector (unique
* relative to the other vectors.)
* @property {PIXI.Graphics} container - The graphics container to
* draw the vector on.
* @property {integer} color - Number representing color to use (e.g.
* 0x000000.)
* @property {?number} [x] - Number representing the x coordinate of
* the vector, relative to the center of the player.
* @property {?number} [y] - Number representing the y coordinate of
* the vector, relative to the center of the player.
*/
/**
* Draw a vector as a small arrow based at the center of the current
* player.
* @private
* @param {VectorInfo} vector
*/
DrawUtils.prototype._drawVector = function(vector) {
var v = new Point(vector.x, vector.y);
if (v.len() < 2) {
this.hideVector(vector.name);
return;
} else {
this.showVector(vector.name);
var v_n = v.normalize();
}
var vectorWidth = 4;
// For arrowhead.
var vectorAngle = Math.atan2(v.y, v.x);
var headAngle = Math.PI / 6;
var headLength = 10;
var leftHead = (new Point(
Math.cos((Math.PI - headAngle + vectorAngle) % (2 * Math.PI)),
Math.sin((Math.PI - headAngle + vectorAngle) % (2 * Math.PI))));
leftHead = leftHead.mul(headLength).add(v);
var rightHead = (new Point(
Math.cos((Math.PI + headAngle + vectorAngle) % (2 * Math.PI)),
Math.sin((Math.PI + headAngle + vectorAngle) % (2 * Math.PI))));
rightHead = rightHead.mul(headLength).add(v);
// For fat vector body.
var leftBase = (new Point(
Math.cos((Math.PI / 2 + vectorAngle) % (2 * Math.PI)),
Math.sin((Math.PI / 2 + vectorAngle) % (2 * Math.PI))));
var rightBase = leftBase.mul(-1);
leftBase = leftBase.mul(vectorWidth / 2);
rightBase = rightBase.mul(vectorWidth / 2);
var end = v_n.mul(v_n.dot(leftHead));
var leftTop = leftBase.add(end);
var rightTop = rightBase.add(end);
// Add shapes to container.
var c = vector.container;
c.clear();
c.lineStyle(2, 0x000000, 1);
c.beginFill(vector.color, 1);
c.moveTo(leftBase.x, leftBase.y);
c.lineTo(leftTop.x, leftTop.y);
c.lineTo(leftHead.x, leftHead.y);
c.lineTo(v.x, v.y);
c.lineTo(rightHead.x, rightHead.y);
c.lineTo(rightTop.x, rightTop.y);
c.lineTo(rightBase.x, rightBase.y);
var v_n_l = v_n.mul(vectorWidth / 2);
var cp1 = rightBase.sub(v_n_l);
var cp2 = leftBase.sub(v_n_l);
c.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, leftBase.x, leftBase.y);
c.endFill();
}
var updateVelocity = function(player, predicted, lastSync) {
function clamp(num, low, high) {
if (num < low)
return low;
if (num > high)
return high;
return num;
}
var dt = (1 / 60);
var damping = 0.5;
if (player.lastSync.lx > lastSync.lx) {
if (window.showDiff && player.lx !== 0 && predicted.lx !== 0) {
console.log(predicted.lx + "\t" + player.lx);
}
if (window.showSync && player.lx !== 0 && predicted.lx !== 0) {
console.log(lastSync.local_lx + "\t" + player.lastSync.lx);
}
predicted.lx = player.lx;
lastSync.lx = player.lastSync.lx;
lastSync.local_lx = lastSync.lx;
}
if (Math.abs(predicted.lx) < player.ms) {
if (player.right) {
predicted.lx += player.ac;
} else if (player.left) {
predicted.lx -= player.ac;
}
// Reduce velocity due to linear damping.
predicted.lx *= clamp((1 - dt * damping), 0, 1);
}
lastSync.local_lx++;
if (player.lastSync.ly > lastSync.ly) {
if (window.showDiff && player.ly !== 0 && predicted.ly !== 0) {
console.log(predicted.ly + "\t" + player.ly);
}
if (window.showSync && player.ly !== 0 && predicted.ly !== 0) {
console.log(lastSync.local_ly + "\t" + player.lastSync.ly);
}
predicted.ly = player.ly;
lastSync.ly = player.lastSync.ly;
lastSync.local_ly = lastSync.ly;
}
if (Math.abs(predicted.ly) < player.ms) {
if (player.down) {
predicted.ly += player.ac;
} else if (player.up) {
predicted.ly -= player.ac;
}
// Reduce velocity due to linear damping.
predicted.lx *= clamp((1 - dt * damping), 0, 1);
}
lastSync.local_ly++;
}
tagpro.ready(function() {
function waitUntilPlayerSprites(callback) {
if (!tagpro.players[tagpro.playerId] || !tagpro.players[tagpro.playerId].sprite || !tagpro.players[tagpro.playerId].sprites) {
setTimeout(function() {
waitUntilPlayerSprites(callback);
}, 250);
} else {
callback();
}
}
function setup() {
var color = 0x00aa00;
var drawutils = new DrawUtils();
drawutils.addVector("velocity", color);
var self = tagpro.players[tagpro.playerId];
var predicted = {
lx: self.lx,
ly: self.ly
};
var sync = {
lx: self.lastSync.lx,
ly: self.lastSync.ly,
local_lx: self.lastSync.lx,
local_ly: self.lastSync.ly
};
setInterval(function() {
updateVelocity(self, predicted, sync);
var velocity = new Point(predicted.lx, predicted.ly);
drawutils.updateVector("velocity", velocity.mul(50));
}, 1e3 / 60);
}
waitUntilPlayerSprites(setup);
});
}
function inject(source) {
source = '(' + source + ')();'
// Create a script node holding this source code.
var script = document.createElement('script');
script.setAttribute("type", "application/javascript");
script.textContent = source;
// Insert the script node into the page, so it will run, and immediately
// remove it to clean up.
document.body.appendChild(script);
document.body.removeChild(script);
}
inject(scriptContent);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment