// ==UserScript==
// @name Diep.io Canvas Helper
// @namespace http://tampermonkey.net/
// @version 2.0.2
// @description canvas manipulation
// @author r!PsAw
// @match https://diep.io/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=diep.io
// @grant none
// @license MIT
// ==/UserScript==
let debug_visible = true; //turn this on to draw lines to shapes & Arrows
/*
- Arrow detection √
- Shapes detection √
(Work in progress...)
- Player Detection
- different Drones Detection
- Bosses Detection
- Turrets Detection
- Text coordinates detect
- Scoreboard reader
*/
//COLOR GETTER SCRIPT (by r!Psaw, aka me :P )
let ui_color_range = {
min: 1,
max: 7
}
let net_color_range = {
min: 0,
max: 27
}
function get_style_color(property){
return getComputedStyle(document.documentElement).getPropertyValue(property).trim();
}
//single use
//let diep_user_colors = update_your_colors();
//loop
let diep_user_colors;
function update_colors(){
window.requestAnimationFrame(update_colors);
if(input && window.lobby_ip){
diep_user_colors = update_your_colors();
//console.log("updated colors:");
//console.log(diep_user_colors);
}
}
window.requestAnimationFrame(update_colors);
function get_hex(convar){
let diep_hex = input.get_convar(convar);
let normal_hex = "#"+diep_hex.split("x")[1];
return normal_hex;
}
function get_hidden(type, number){
type === "UI"?(ui_color_range.min <= number && number <= ui_color_range.max)?null:console.log("illegal Number!"):type === "NET"?(net_color_range.min <= number && number <= net_color_range.max)?null:("illegal Number!"):console.log("illegal Type!");
switch (type){
case "UI":
return get_style_color(`--uicolor${number}`);
break
case "NET":
return get_style_color(`--netcolor${number}`);
break
}
}
function update_your_colors(){
let temp_container = {
background: get_hex("ren_background_color"),
bar_background: get_hex("ren_bar_background_color"),
border: get_hex("ren_border_color"),
grid: get_hex("ren_grid_color"),
healthbar_back: get_hex("ren_health_background_color"),
healthbar_front: get_hex("ren_health_fill_color"),
minimap: get_hex("ren_minimap_background_color"),
minimap_border: get_hex("ren_minimap_border_color"),
scorebar: get_hex("ren_score_bar_fill_color"),
solid_border: get_hex("ren_stroke_solid_color"),
xp_bar: get_hex("ren_xp_bar_fill_color"),
ui1: get_hidden("UI", 1),
ui2: get_hidden("UI", 2),
ui3: get_hidden("UI", 3),
ui4: get_hidden("UI", 4),
ui5: get_hidden("UI", 5),
ui6: get_hidden("UI", 6),
ui7: get_hidden("UI", 7),
smasher_and_dominator: get_hidden("NET", 0),
barrels: get_hidden("NET", 1),
body: get_hidden("NET", 2),
blue_team: get_hidden("NET", 3),
red_team: get_hidden("NET", 4),
purple_team: get_hidden("NET", 5),
green_team: get_hidden("NET", 6),
shiny_shapes: get_hidden("NET", 7),
square: get_hidden("NET", 8),
triangle: get_hidden("NET", 9),
pentagon: get_hidden("NET", 10),
crasher: get_hidden("NET", 11),
arena_closers_neutral_dominators: get_hidden("NET", 12),
scoreboard_ffa_etc: get_hidden("NET", 13),
maze_walls: get_hidden("NET", 14),
others_ffa: get_hidden("NET", 15),
necromancer_squares: get_hidden("NET", 16),
fallen_bosses: get_hidden("NET", 17)
}
return temp_container;
};
//Actual script:
//variables
const crx = CanvasRenderingContext2D.prototype;
let methods = [
'beginPath',
'setTransform',
'drawImage',
'arc',
'moveTo',
'lineTo',
'fill',
'fillRect',
'fillText',
'stroke',
'strokeRect',
'strokeText',
'clearRect'
];
let patterns = {
arc: ['setTransform', 'arc', 'fill'],
triangle: ["setTransform", "moveTo", "lineTo", "lineTo", "fill"],
square: ["setTransform", "moveTo", "lineTo", "lineTo", "lineTo", "fill"],
pentagon: ["setTransform", "moveTo", "lineTo", "lineTo", "lineTo", "lineTo", "fill"],
}
let bosses = {
fallen_booster: {x: null, y: null},
fallen_ol: {x: null, y: null},
necromancer: {x: null, y: null},
guardian: {x: null, y: null}
}
let drones = {
over: [], //overlord, overseer, manager
necro: [],
battleship: [],
base: [],
summoner: [],
guardian: []
}
let shapes = {
squares: [],
crashers: [],
triangles: [],
pentagons: [],
}
let arrows = {
leader: {
moveTo: [0, 0],
lineTo1: [0, 0],
lineTo2: [0, 0],
center: [0, 0]
},
minimap: {
moveTo: [0, 0],
lineTo1: [0, 0],
lineTo2: [0, 0],
center: [0, 0]
},
dimension: {//invisible arrow, used to determine minimap size
moveTo: [0, 0],
lineTo1: [0, 0],
lineTo2: [0, 0],
center: [0, 0]
},
}
let placeholder = [// script will work with this data, to store it in the actual one. This is done, because canvas api is retarded
bosses,
drones,
shapes,
arrows
];
//classes
class Proxy_communicator {
constructor(){
this.last = null;
this.order = [];
this.lines = []; //store xy from lineTo's here, because lineTo proxy class can only save 1 at the time
this.transform = [0, 0, 0, 0, 0, 0];
}
announce(proxy_class){
if(is_beginPath(proxy_class.name)){
this.last = proxy_class;
this.order = [];
return
}
this.last = proxy_class;
this.order.push(proxy_class.name);
/*
console.log(`
announced:
last ${this.last.name}
order ${this.order}
`);
*/
}
has_pattern(pattern){
//console.log('used pattern:');
//console.log(pattern);
//console.log('current order:');
//console.log(this.order);
let o_l = this.order.length;
let p_l = pattern.length;
/*
console.log(`
order length: ${o_l}
pattern length: ${p_l}
`);
*/
let counter = 0;
if(o_l != p_l){
return false;
}
//console.log('first condition met!');
for(let i = 0; i < o_l; i++){
/*
console.log(`
i ${i}
pattern[i] ${pattern[i]}
this.order[i] ${this.order[i]}
counter ${counter}
`);
*/
if(pattern[i] === this.order[i]){
counter++;
}
}
//console.log(`final ${counter === p_l}`);
return (counter === p_l);
}
}
class Proxy_class {
constructor(method){
this.name = method;
this.calls = 0;
this.target = null;
this.thisArgs = null;
this.args = null;
this.screenXY = [null, null];
}
update_tta(target, thisArgs, args){
if(is_beginPath(this.name)){
//console.log(`beginPath detected, resetting and quitting...`);
reset_calls();
return
}
/*
console.log(`
update_tta called on Proxy_class.name ${this.name}
with arguments:
${target}
${thisArgs}
${args}
`);
*/
this.target = target;
this.thisArgs = thisArgs;
this.args = args;
}
}
const communicator = new Proxy_communicator();
let method_classes = [];
//functions
function is_beginPath(name){
//console.log(`is_beginPath called with ${name}`);
return (name === 'beginPath');
}
function start_proxies(){
//console.log('start_proxies called');
methods.forEach(start_proxy);
}
function define_current(method){
/*
console.log(`
define_current called with
${method}
returning argument:
${method_classes.find((element) => element.name === method)}
`);
*/
return method_classes.find((element) => element.name === method);
}
function start_proxy(method){
//console.log(`start_proxy called with ${method}`);
//define class outside proxies, to avoid repetition
let current = new Proxy_class(method);
method_classes.push(current);
//console.log(`${current} created a new Proxy_class & pushed inside method_classes, check:`);
//console.log(method_classes);
crx[method] = new Proxy(crx[method], {
apply(target, thisArgs, args) {
/*
if(diep_user_colors && thisArgs.fillStyle === diep_user_colors.triangle){
console.log(`color detected at ${method}. Args: ${args}`);
}
*/
current = define_current(method);
//console.log(`current redefined inside proxy: ${method} to ${current}`);
current.update_tta(target, thisArgs, args);
current.calls++;
communicator.announce(current);
handle_proxy(method, {target: target, thisArgs: thisArgs, args: args});
//logic
return Reflect.apply(target, thisArgs, args);
}
});
}
function handle_proxy(type, values){
//console.log(`working on proxy ${type}`);
let current = define_current(type);
let x, y;
switch(type){
case 'setTransform':
communicator.setTransform = values.args;
break
case 'fill':
if(communicator.has_pattern(patterns.arc)){
x = communicator.setTransform[4];
y = communicator.setTransform[5];
let target = define_current('arc');
target.screenXY = [x, y];
debug({x:x,y:y});
//console.log(`arc updated coords ${target.screenXY}`);
}else if(communicator.has_pattern(patterns.triangle)){
//console.log(values.thisArgs.fillStyle);
//console.log(values.thisArgs.globalAlpha);
if(is_arrow(values.thisArgs)){
switch(true){
case (values.thisArgs.globalAlpha === 0.3499999940395355):
update_arrow('leader');
break
case (values.thisArgs.globalAlpha > 0.9):
update_arrow('minimap');
break
}
}
if(is_shape('triangle', values.thisArgs)){
//console.log('TRIANGLE FOUND');
update_shape('triangle');
}
if(is_shape('crasher', values.thisArgs)){
//console.log('TRIANGLE FOUND');
update_shape('crasher');
}
//drone detection logic...
}else if(communicator.has_pattern(patterns.square)){
if(is_shape('square', values.thisArgs)){
//console.log('SQUARE FOUND');
update_shape('square');
}
//drone detection logic...
}else if(communicator.has_pattern(patterns.pentagon)){
if(is_shape('pentagon', values.thisArgs)){
//console.log('PENTAGON FOUND');
update_shape('pentagon');
}
}
break
case 'moveTo':
current.screenXY = [values.args[0], values.args[1]];
//console.log(`moveTo updated coords ${current.screenXY}`);
//logic for shape & arrow recognition
break
case 'lineTo':
current.screenXY = [values.args[0], values.args[1]];
//console.log(`lineTo updated coords ${current.screenXY}`);
communicator.lines.push(current.screenXY);
//console.log('lineTo updated inside communicator:');
//console.log(communicator.lines);
//logic for shape & arrow recognition
break
case 'drawImage':
x = communicator.setTransform[4];
y = communicator.setTransform[5];
current.screenXY = [x, y];
break
default:{
//add logic
}
}
}
function is_arrow(context){
return (context.fillStyle === '#000000');
}
function is_shape(type, context){
if(diep_user_colors){
return (diep_user_colors[type] === context.fillStyle);
}
return false;
}
function get_average(points){
let result = [0, 0];
for(let point of points){
result[0] += point[0];
result[1] += point[1];
}
result[0] /= points.length;
result[1] /= points.length;
return result;
}
function update_shape(type){
let plural = type + 's'; //triangle = shapes.triangles
//basically constructing the shape from information stored
let moveTo = define_current('moveTo').screenXY;
let points = [moveTo];
let point_num = 1;
let shape = new Map(); //used a map instead of Array, because I can't push a key with a value
shape.set('moveTo', moveTo);
for(let line of communicator.lines){
points.push(line);
shape.set(`lineTo${point_num}`, line);
point_num++;
}
//console.log(points);
shape.set('center', get_average(points));
debug({x: shape.get('center')[0], y: shape.get('center')[1]});
//console.log(shape);
//adding the new made shape inside global array of shapes
placeholder.shapes[plural].push(shape);
//console.log(shapes[plural]);
}
function update_arrow(type){
let moveTo = define_current('moveTo');
let points = [moveTo.screenXY, communicator.lines[0], communicator.lines[1]];
placeholder.arrows[type].moveTo = points[0];
placeholder.arrows[type].lineTo1 = points[1];
placeholder.arrows[type].lineTo2 = points[2];
placeholder.arrows[type].center = get_average(points);
debug({x: placeholder.arrows[type].center[0], y: placeholder.arrows[type].center[1]});
//let ctx = canvas.getContext('2d');
//ctx.fillRect(Arrows[type].center[0], Arrows[type].center[1], 150, 150);
//console.log(Arrows[type]);
}
function placeholder_apply() {
// Ensure `placeholder` properties exist before copying
if (!placeholder.bosses) placeholder.bosses = {};
if (!placeholder.shapes) placeholder.shapes = { squares: [], crashers: [], triangles: [], pentagons: [] };
if (!placeholder.drones) placeholder.drones = { over: [], necro: [], battleship: [], base: [], summoner: [], guardian: [] };
if (!placeholder.arrows) placeholder.arrows = { leader: {}, minimap: {}, dimension: {} };
// Manually deep copy objects to avoid JSON issues
bosses = structuredClone(placeholder.bosses);
shapes = structuredClone(placeholder.shapes);
drones = structuredClone(placeholder.drones);
arrows = structuredClone(placeholder.arrows);
// Reset placeholder arrays properly
for (let key in placeholder) {
if (typeof placeholder[key] === 'object' && placeholder[key] !== null) {
for (let key2 in placeholder[key]) {
if (Array.isArray(placeholder[key][key2])) {
placeholder[key][key2] = []; // Reset nested arrays
}
}
}
}
}
function reset_coords(){
window.requestAnimationFrame(reset_coords);
//console.log('called reset_coords');
placeholder_apply();
method_classes.forEach(reset_coord);
}
window.requestAnimationFrame(reset_coords);
function reset_coord(method_class){
//console.log(`called reset_coord with ${method_class}`);
method_class.screenXY = [null, null];
}
function reset_calls(){
//console.log(`called reset_calls`);
communicator.lines = [];
method_classes.forEach(reset_call);
}
function reset_call(method_class){
//console.log(`called reset_call with ${method_class}`);
method_class.calls = 0;
}
function array_or_map(object){
let answer = 'neither';
(object instanceof Map) ? answer = 'Map' : (object instanceof Array)? answer = 'Array' : null;
return answer;
}
function debug(to, from = {x: canvas.width/2, y: canvas.height/2}){
if(!debug_visible){
return;
}
let ctx = canvas.getContext('2d');
ctx.moveTo(from.x, from.y);
ctx.lineTo(to.x, to.y);
ctx.stroke();
}
/* === external operations === */
function calculate_distance(x1, y1, x2, y2){
return Math.sqrt((x2 - x1)^2 + (y2 - y1)^2);
}
function get_closest(array){
let pos = {
x: canvas.width/2,
y: canvas.height/2
};
let smallest = {
element: null,
distance: canvas.width + canvas.height
}
let l = array.length;
for(let i = 0; i < l; i++){
let target = array[i];
if(array_or_map(target) === 'Map'){
target = array[i].get('center');
}
let d = calculate_distance(pos.x, pos.y, target[0], target[1]);
if(smallest.distance > d){
smallest.element = target;
smallest.distance = d;
}
}
return smallest.element;
}
function find_text(text, mode = 'strict', save = 'no'){
//Note: text that is being updated very quickly (like Lvl 1 Tank) will not get detected properly
let current = define_current('fillText');
let result;
if(mode === 'strict'){
(current.args[0]===text) ? result = current.args[0] : null;
}else{
(current.args[0].includes(text)) ? result = current.args[0] : null;
}
return result;
}
//init
start_proxies();