// ==UserScript==
// @name florr.io hack
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the florr.io!
// @author m28
// @match https://florr.io/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// some settings
const client = {
autoRespawn: {
enabled: 0, // set to 1 to autoRespawn in spawnBiome
spawnBiome: 'Hel'
},
bypassAfkCheck: {
movementCheck: 1,
afkButton: 1,
},
autoGrind: {
enabled: 0 // set to 1 to enable autoGrind. also set autoRespawn.enabled to 1
},
tracers: 1, // draw tracers
}
let respawnState = 0 // keep track of whether need to press Continue or Ready
let lastCheck = 0 // 100 timer
// use original functions, add prefix
const _console = {
_log: window.console.log,
log: function() {
this._log(`%c[FlorrScript]`, `color: rgb(30, 150, 30); background: rgb(215, 255, 205)`, ...arguments)
}
}
// array multiplier for canvas untransform
const multiply = function(t,l){let e=t.length,n=t[0].length,$=l[0].length,r=Array(e);for(let f=0;f<e;++f){let o=Array($);r[f]=o;let g=t[f];for(let h=0;h<$;++h){let u=0;for(let i=0;i<n;++i)u+=g[i]*l[i][h];o[h]=u}}return r}
// proxy identity function that does nothing
let identity = function(a, b, c) {
return Reflect.apply(a, b, c)
}
let beforeAnimationFrame = identity
// keep track of tracers
let tracers = {}, addTracer = function(t, color) {
if(!color) { color = '#000000' } // use black if no color
if(!tracers[color]) { tracers[color] = [] }
tracers[color].push(t)
}, mobs = []
const parseColor = function(str) { // convert hex to [r, g, b]
return [parseInt(str[1] + str[2], 16), parseInt(str[3] + str[4], 16), parseInt(str[5] + str[6], 16)]
}
// mouse position relative to screen center
let mouse = {
dx: 0,
dy: 0
}
const main = function() {
const canvas = document.getElementById('canvas')
// record actual mouse position
canvas._addEventListener('mousemove', function(e) {
mouse.dx = (e.clientX - window.innerWidth * 0.5)
mouse.dy = (e.clientY - window.innerHeight * 0.5)
})
const ctx = canvas.getContext('2d')
beforeAnimationFrame = function(a, b, c) {
// we want to run this right after the rendering function
let n = performance.now()
let w = canvas.width * 0.5, h = canvas.height * 0.5
// converting between dom coordinates and canvas coordinates
let ir = 1 / window.devicePixelRatio
let dw = w * ir, dh = h * ir
if(client.autoGrind.enabled) {
// listeners.keydown({ keyCode:16, preventDefault:function() {} })
let closestMob = false, closestDistance = -1
// finding closest mob, doesn't include flowers
for(let i=mobs.length-1;i>=0;i--) {
let m = mobs[i]
let dx = m[0] - w, dy = m[1] - h
let d = dx * dx + dy * dy
if(d < closestDistance || closestDistance < 0) {
closestDistance = d
closestMob = [dx, dy]
}
}
if(closestMob) {
let d = 100 * (closestDistance < 1 ? 1 : 1 / Math.sqrt(closestDistance))
// move mouse to run toward mob
mouse.dx = closestMob[0] * d
mouse.dy = closestMob[1] * d
}
}
// if Ready button not present reset state
if(!buttons.Ready || !client.autoRespawn.enabled) { respawnState = 0 }
if(n - lastCheck > 100) {
// runs every 100 ms
lastCheck = n
!function() {
if(client.autoRespawn.enabled) {
if(respawnState < 1) {
// change biome to specified biome
if(buttons[client.autoRespawn.spawnBiome]) {
if(clickButton(client.autoRespawn.spawnBiome)) {
respawnState ++
}
return
}
} else {
if(buttons.Ready) {
// click Ready button
clickButton('Ready')
return
}
} if(buttons.Continue) {
// died, click Continue button
clickButton('Continue')
// reset state to make sure correct biome
respawnState = 0
}
}
if(client.bypassAfkCheck.afkButton) {
// not fully working
clickButton(`I'm here`)
}
}()
} else if(!buttons.Continue && (client.bypassAfkCheck.movementCheck || client.autoGrind.enabled)) {
let a = (n / 1000) % (2 * Math.PI)
let tx = mouse.dx + dw + Math.sin(a), ty = mouse.dy + dh + Math.cos(a)
// bypass afk check
listeners.mousemove({
clientX: tx,
screenX: tx,
clientY: ty,
screenY: ty
})
}
let transform = ctx.getTransform()
ctx.translate(w, h)
ctx.lineCap = 'round'
ctx.miterLimit = 1.68
ctx.font = '14px Ubuntu'
for(let color in tracers) {
let o = tracers[color]
for(let i=o.length-1;i>=0;i--) {
let t = o[i]
let l = 1, a = 1
// lower rarities are transparent, higher rarities thicker lines
if(t[2] > 3) {
l += (t[2] - 3) * 0.5
} else {
a = (t[2] + 1) / 4
}
let j = a
// draw dashed line at 50% opacity
a *= 0.5
let r = parseColor(color)
ctx.strokeStyle = `rgba(${r[0]}, ${r[1]}, ${r[2]}, ${a})`
ctx.setLineDash([10, 15])
ctx.lineWidth = l
ctx._beginPath()
let dx = t[0] - w, dy = t[1] - h
ctx._moveTo(dx, dy)
let d = dx * dx + dy * dy
ctx._lineTo(0, 0)
ctx._stroke()
// draw solid line at 25% opacity of dashed line
a *= 0.25
ctx.strokeStyle = `rgba(${r[0]}, ${r[1]}, ${r[2]}, ${a})`
ctx.setLineDash([])
ctx._stroke()
ctx._closePath()
if(d > 300 * 300) {
// target distance over 300, draw number
let rd = Math.sqrt(d)
if(rd < 350) {
// between 300 and 350 make it transparent
j *= (rd - 300) * 0.02
}
if(j > 0.05) {
// if under 5% don't draw
d = 1 / rd
let y = 300 + (rd - 300) / (rd - 100) * 100
// calculate text position
let tx = dx * d * y
let ty = dy * d * y + 7
// if rgb(0, 0, 0) use white instead
if(r[0] === 0 && r[1] === 0 && r[2] === 0) { r[0] = r[1] = r[2] = 255 }
ctx.fillStyle = `rgb(${r[0]}, ${r[1]}, ${r[2]})`
ctx.strokeStyle = '#000000'
ctx.lineWidth = 1.68
// use j^2 for opacity to make gradient steeper
j *= j
// when over 95% use 100% for better performance
if(j < 0.95) { ctx.globalAlpha = j }
// number is how many 100's away target is
let text = '' + Math.round(rd / 100)
ctx.textAlign = 'center'
// stroke then fill
ctx._strokeText(text, tx, ty)
ctx.lineWidth = 10
ctx._fillText(text, tx, ty)
if(j < 0.95) { ctx.globalAlpha = 1 }
}
}
}
}
ctx.setTransform(transform)
let f = c[0]
if(f.proxy) { c[0] = f.proxy } else {
c[0] = f.proxy = new Proxy(f, { apply:function(a, b, c) {
// we want to run these right before the rendering function
lbuttons = buttons
buttons = {}
tracers = {}
mobs = []
window.l = listeners
window.b = buttons
return Reflect.apply(a, b, c)
} })
}
return Reflect.apply(a, b, c)
}
}
// might be used in the future
window.console.log = new Proxy(window.console.log, { apply:function(a, b, c) {
return Reflect.apply(a, b, c)
} })
const untransform = function(x, y, t) {
let r = multiply([[x, y, 1]], [[t.a, t.b, 0], [t.c, t.d, 0], [t.e, t.f, 1]])[0]
return [r[0] / r[2], r[1] / r[2]]
}
let lastText = [], lastWhiteText = []
// rarity data, useful for determining of some text is a valid rarity type
let rarities = {
Common: {
name: 'Common',
color: '#7eef6d',
index: 0
},
Unusual: {
name: 'Unusual',
color: '#ffe65d',
index: 1
},
Rare: {
name: 'Rare',
color: '#4d52e3',
index: 2
},
Epic: {
name: 'Epic',
color: '#861fde',
index: 3
},
Legendary: {
name: 'Legendary',
color: '#de1f1f',
index: 4
},
Mythic: {
name: 'Mythic',
color: '#1fdbde',
index: 5
},
Ultra: {
name: 'Ultra',
color: '#ff2b75',
index: 6
},
Super: {
name: 'Super',
color: '#000000',
index: 7
}
}
let colors = {}
// reversing table for better performance
for(let r in rarities) {
colors[rarities[r].color] = rarities[r]
}
// funny text modification callback
const textTransform = function(text, ctx) {
if(text === 'Plinko') {
text = 'Scam Machine'
}
return text
}
let buttons = {}, lbuttons = {}
// proxy measureText to keep results consistent with fillText and strokeText
window.CanvasRenderingContext2D.prototype._measureText = window.CanvasRenderingContext2D.prototype.measureText
window.CanvasRenderingContext2D.prototype.measureText = new Proxy(window.CanvasRenderingContext2D.prototype.measureText, { apply:function(a, b, c) {
c[0] = textTransform(c[0], b)
return Reflect.apply(a, b, c)
} })
// proxy strokeText to keep results consistent with fillText
window.CanvasRenderingContext2D.prototype._strokeText = window.CanvasRenderingContext2D.prototype.strokeText
window.CanvasRenderingContext2D.prototype.strokeText = new Proxy(window.CanvasRenderingContext2D.prototype.strokeText, { apply:function(a, b, c) {
c[0] = textTransform(c[0], b)
return Reflect.apply(a, b, c)
} })
// proxy fillText for tracer detection
window.CanvasRenderingContext2D.prototype._fillText = window.CanvasRenderingContext2D.prototype.fillText
window.CanvasRenderingContext2D.prototype.fillText = new Proxy(window.CanvasRenderingContext2D.prototype.fillText, { apply:function(a, b, c) {
if(lastText[1]) {
// something detected here
if(colors[b.fillStyle] && b.globalAlpha >= 1 && lastPaths[0][3] && client.tracers) {
let t = lastPaths[0]
if(c[0].startsWith('Lvl ') && parseInt(c[0].slice(4)) >= 0) {
// flower found. treat it like a mob with rarity determined by level
t = untransform((t[0] + t[1]) * 0.5, t[2], t[3])
// tracer is black
addTracer([t[0], t[1], colors[b.fillStyle].index], '#000000')
lastPaths = [[], [], []]
} else if(rarities[c[0]]) {
// mob found. also includes player summons like egg beetles
t = untransform((t[0] + t[1]) * 0.5, t[2], t[3])
// tracer color same as rarity
addTracer([t[0], t[1], colors[b.fillStyle].index], b.fillStyle)
mobs.push([t[0], t[1]])
lastPaths = [[], [], []]
}
}
}
// this fillText might be a button
let bd = buttonData[c[0]]
if(bd && (!bd.color || bd.color === b.fillStyle) && (!bd.font || bd.font === b.font)) {
// button found
let t = untransform(c[1], c[2], b.getTransform())
// if this button was existing last render
let o = lbuttons[c[0]]
let n = buttons[c[0]] = {
x: t[0],
y: t[1],
d: 1, // we don't want to click buttons that are moving. only click when d < 0.01
s: performance.now(), // we want to wait 2000 ms before we click any button
fillStyle: b.fillStyle,
font: b.font
}
if(o) {
n.d = Math.abs(n.x - o.x) + Math.abs(n.y - o.y) // calculate speed
n.s = o.s // this button alr exists so its creation time is lower
}
}
if(c[0] === `I'm here` && client.bypassAfkCheck.afkButton) {
afkButton(untransform(c[1], c[2], b.getTransform()))
}
// for referencing next fillText
lastText = [c, b.fillStyle]
if(b.fillStyle === '#ffffff') {
lastWhiteText = [c, b.fillStyle]
}
c[0] = textTransform(c[0], b)
return Reflect.apply(a, b, c)
} })
let clicking = false
// I'm here button clicker
const afkButton = function(t) {
if(clicking) { return }
clicking = true
setTimeout(function() {
clicking = false
clickAt(t.x, t.y)
}, 500 + 2000 * Math.random())
}
// buttons we want to look for
let buttonData = {
Ready: {
color: '#ffffff',
font: '27.5px Ubuntu'
},
Garden: {
color: '#ffffff',
font: '16px Ubuntu'
},
Desert: {
color: '#ffffff',
font: '16px Ubuntu'
},
Ocean: {
color: '#ffffff',
font: '16px Ubuntu'
},
Jungle: {
color: '#ffffff',
font: '16px Ubuntu'
},
Hel: {
color: '#ffffff',
font: '16px Ubuntu'
},
'Play as guest': {},
Continue: {
color: '#ffffff',
}
}
// keep track of paths, we can find health bars
let lastPaths = [[], [], []], lastFill = ''
let path = [], topPath = false, addSegment = function(s) {
if(topPath) {
topPath.push(s)
} else {
path.push(topPath = [s])
}
}
// proxy beginPath
window.CanvasRenderingContext2D.prototype._beginPath = window.CanvasRenderingContext2D.prototype.beginPath
window.CanvasRenderingContext2D.prototype.beginPath = new Proxy(window.CanvasRenderingContext2D.prototype.beginPath, { apply:function(a, b, c) {
path = []
topPath = false
// path restart
return Reflect.apply(a, b, c)
} })
// proxy moveTo
window.CanvasRenderingContext2D.prototype._moveTo = window.CanvasRenderingContext2D.prototype.moveTo
window.CanvasRenderingContext2D.prototype.moveTo = new Proxy(window.CanvasRenderingContext2D.prototype.moveTo, { apply:function(a, b, c) {
addSegment({
type: 'moveTo',
x: c[0],
y: c[1]
})
return Reflect.apply(a, b, c)
} })
// proxy lineTo
window.CanvasRenderingContext2D.prototype._lineTo = window.CanvasRenderingContext2D.prototype.lineTo
window.CanvasRenderingContext2D.prototype.lineTo = new Proxy(window.CanvasRenderingContext2D.prototype.lineTo, { apply:function(a, b, c) {
addSegment({
type: 'lineTo',
x: c[0],
y: c[1]
})
return Reflect.apply(a, b, c)
} })
// proxy closePath
window.CanvasRenderingContext2D.prototype._closePath = window.CanvasRenderingContext2D.prototype.closePath
window.CanvasRenderingContext2D.prototype.closePath = new Proxy(window.CanvasRenderingContext2D.prototype.closePath, { apply:function(a, b, c) {
path = []
topPath = false
// path destroyed
return Reflect.apply(a, b, c)
} })
// proxy stroke
window.CanvasRenderingContext2D.prototype._stroke = window.CanvasRenderingContext2D.prototype.stroke
window.CanvasRenderingContext2D.prototype.stroke = new Proxy(window.CanvasRenderingContext2D.prototype.stroke, { apply:function(a, b, c) {
// shift paths
if(path.length === 1 && path[0].length === 2 && path[0][0].y === path[0][1].y) {
lastPaths[0] = lastPaths[1]
lastPaths[1] = lastPaths[2]
lastPaths[2] = [path[0][0].x, path[0][1].x, path[0][0].y, b.getTransform()]
}
return Reflect.apply(a, b, c)
} })
// proxy fill
window.CanvasRenderingContext2D.prototype._fill = window.CanvasRenderingContext2D.prototype.fill
window.CanvasRenderingContext2D.prototype.fill = new Proxy(window.CanvasRenderingContext2D.prototype.fill, { apply:function(a, b, c) {
lastFill = b.fillStyle
return Reflect.apply(a, b, c)
} })
// proxy requestAnimationFrame for render hooks
window.requestAnimationFrame = new Proxy(window.requestAnimationFrame, { apply:function(a, b, c) {
return beforeAnimationFrame(a, b, c)
} })
// wait for load
const interval = setInterval(function() {
if(document.body) {
clearInterval(interval)
main()
}
})
// we can force an arraybuffer instantiation if want
if(0) {
window.WebAssembly.instantiateStreaming = new Proxy(window.WebAssembly.instantiateStreaming, { apply:function(a, b, c) {
let d = new Response()
c[0] = d
return Reflect.apply(a, b, c)
} })
}
const listeners = {}, trigger = function(type, data) {
if(listeners[type]) { listeners[type](data) }
}
// universal hook for event listeners
const listenerApply = function(a, b, c) {
if(c[0] === 'mousemove') {
listeners.mousemove = c[1]
}
if(c[0] === 'blur' || c[0] === 'focus' || c[0] === 'visibilitychange') {
// makes it easier to afk and stuff
return
}
if(b && b.id === 'canvas') {
if(c[0] === 'mousedown') {
listeners.mousedown = c[1]
}
}
if(c[0] === 'mouseup') {
listeners.mouseup = c[1]
}
if(c[0] === 'keydown') {
listeners.keydown = c[1]
}
if(c[0] === 'keyup') {
listeners.keyup = c[1]
}
return Reflect.apply(a, b, c)
}
const clickAt = function(x, y) {
let ir = 1 / window.devicePixelRatio
x *= ir
y *= ir
// move mouse
listeners.mousemove({
clientX: x,
screenX: x,
clientY: y,
screenY: y
})
// mouse down
listeners.mousedown({
preventDefault: function() {},
clientX: x,
clientY: y
})
// mouse up
listeners.mouseup({
preventDefault: function() {},
clientX: x,
clientY: y
})
}
const clickButton = function(text) {
if(buttons[text]) {
if(buttons[text].d > 0.01) {
// moving fast, don't click moving buttons
return
}
let n = performance.now()
if(n - buttons[text].s < 2000) {
// at least 2000 ms until clickable
return
}
clickAt(buttons[text].x, buttons[text].y)
return true
}
}
// hook event listeners
HTMLElement.prototype._addEventListener = HTMLElement.prototype.addEventListener
HTMLElement.prototype.addEventListener = new Proxy(HTMLElement.prototype.addEventListener, { apply:listenerApply })
window.addEventListener = new Proxy(window.addEventListener, { apply:listenerApply })
document.addEventListener = new Proxy(document.addEventListener, { apply:listenerApply })
localStorage.florrio_tutorial = 'complete'
})();