// ==UserScript== // @name florr.io | Petal farming progress counter // @namespace Furaken // @version 1.3.5 // @description Track and count the number of desired petals. // @author Furaken, Max Nest // @match https://florr.io/* // @grant unsafeWindow // @license AGPL3 // @run-at document-start // @downloadURL none // ==/UserScript== (async function() { 'use strict'; const inventoryBaseAddress = await new Promise((resolve, reject) => { function readVarUint32(arr) { let idx = 0, res = 0; do res |= (arr[idx] & 0b01111111) << idx * 7; while (arr[idx++] & 0b10000000); return [idx, res]; } WebAssembly.instantiateStreaming = (src, imports) => src.arrayBuffer().then(buf => WebAssembly.instantiate(buf, imports)); const _instantiate = WebAssembly.instantiate; WebAssembly.instantiate = (buf, imports) => { const arr = new Uint8Array(buf); const addrs = []; for (let i = 0; i < arr.length; i++) { let j = i; if (arr[j++] !== 0x41) continue; // i32.const if (arr[j++] !== 1) continue; // 1 if (arr[j++] !== 0x3a) continue; // i32.store8 if (arr[j++] !== 0) continue; // align=0 if (arr[j++] !== 0) continue; // offset=0 if (arr[j++] !== 0x41) continue; // i32.const const [offset, addr] = readVarUint32(arr.subarray(j)); j += offset; if (arr[j++] !== 0x41) continue; // i32.const if (arr[j++] !== 5) continue; // 5 if (arr[j++] !== 0x36) continue; // i32.store if (arr[j++] !== 2) continue; // align=2 if (arr[j++] !== 0) continue; // offset=0 addrs.push(addr >> 2); } if (addrs.length === 1) resolve(addrs[0]); else reject(new Error('Failed to get inventory base address')); return _instantiate(buf, imports); }; }) function syntaxHighlight(json) { // https://stackoverflow.com/questions/4810841/pretty-print-json-using-javascript if (typeof json != 'string') { json = JSON.stringify(json, undefined, 2); } json = json.replace(/&/g, '&').replace(//g, '>') return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { var c = color.json.number if (/^"/.test(match)) { if (/:$/.test(match)) { c = color.json.key } else { c = color.json.string } } else if (/true|false/.test(match)) { c = color.json.boolean } else if (/null/.test(match)) { c = color.json.null } return `${match}`; }); } function abbNum(value) { if (lcs_.exactNumber) return value else { return Math.abs(Number(value)) >= 1.0e+9 ? (Math.abs(Number(value)) / 1.0e+9).toFixed(2) + "b" : Math.abs(Number(value)) >= 1.0e+6 ? (Math.abs(Number(value)) / 1.0e+6).toFixed(2) + "m" : Math.abs(Number(value)) >= 1.0e+3 ? (Math.abs(Number(value)) / 1.0e+3).toFixed(2) + "k" : Math.abs(Number(value)) } } class ElementCreate { constructor(tag) { this.element = document.createElement(tag) } attr(attributes) { for (const [key, value] of Object.entries(attributes)) this.element.setAttribute(key, value) return this } style(styles) { for (const [property, value] of Object.entries(styles)) this.element.style[property] = value return this } content(content) { if (typeof content == 'string') this.element.innerHTML = content else if (content instanceof HTMLElement) this.element.appendChild(content) return this } append(parent) { const parentElement = typeof parent == 'string' ? document.querySelector(parent) : parent parentElement.appendChild(this.element) return this } get() { return this.element } } function syncLcs() { localStorage.__petalCounter = JSON.stringify(lcs_) } function findSequence(seq, mem) { // findSequence() by Max Nest let match = 0 for (let addr = 0; addr < mem.length; addr++) { if (mem[addr] === seq[match]) match++ else if (mem[addr] === seq[0]) match = 1 else match = 0 if (match === seq.length) return addr - match + 1 } } function getPetalAddr(id, thisRarity, inventoryBaseAddress) { return inventoryBaseAddress + ((id + 1) * kRarity.length) - (kRarity.length - thisRarity) } let kRarity = [ { name: 'Common', color: 0x7EEF6D }, { name: 'Unusual', color: 0xFFE65D }, { name: 'Rare', color: 0x4D52E3 }, { name: 'Epic', color: 0x861FDE }, { name: 'Legendary', color: 0xDE1F1F }, { name: 'Mythic', color: 0x1FDBDE }, { name: 'Ultra', color: 0xFF2B75 }, { name: 'Super', color: 0x2BFFA3 }, { name: 'Unique', color: 0x555555 } ] const color = { background: '#202020', darker: '#1d1d1d', primary: '#bb86fc', secondary: '#03dac5', tertiary: '#ff0266', gray: '#666666', json: { string: '#ff0266', number: '#03dac5', boolean: '#bb86fc', null: '#bb86fc', key: '#9cdcfe' } } const defaultLCS = { toggleKey: { Ctrl: false, Shift: false, Alt: false, Meta: false, key: ['=', 0], code: 'Equal' }, exactNumber: false, count: { petal: {} } } let lcs_ = localStorage.__petalCounter || defaultLCS if (typeof lcs_ == 'string') lcs_ = JSON.parse(lcs_) for (const [key, value] of Object.entries(defaultLCS)) { if (lcs_[key] == null) lcs_[key] = value } localStorage.__petalCounter = JSON.stringify(lcs_) var isKeyPressed = { toggleKey: true }, module, kPetals, florrioUtils, image = { petal: [], blank: [] } let interval1 = setInterval(() => { if (!florrioUtils) { florrioUtils = unsafeWindow?.florrio?.utils if (!florrioUtils) return kPetals = { sidByNameOrder: florrioUtils.getPetals().map(x => [x.sid, x.sid.split('_')[x.sid.split('_').length - 1]]).sort(function (a, b) { if (a[1] > b[1]) return 1 else return -1 }).map(x => x[0]), sid: florrioUtils.getPetals().map(x => x.sid) } let thisRarity = new Array(florrioUtils.getPetals().find(x => x.allowedDropRarities != null).allowedDropRarities.length).fill({ name: '?', color: 0 }) thisRarity.forEach((x, i) => { if (kRarity[i]) thisRarity[i] = kRarity[i] }) kRarity = thisRarity for (let r = 0; r < kRarity.length; r++) image.blank[r] = florrioUtils.generateMobImage(128, florrioUtils.getMobs().find(x => x.sid == 'titan').id, r, 1) for (let p = 0; p < kPetals.sid.length; p++) image.petal[p] = florrioUtils.generatePetalImage(128, p + 1, kRarity.length - 1, 1) module = Module.HEAPU32 updateProgress() newPetal() clearInterval(interval1) } }) setInterval(() => { module = Module.HEAPU32 updateProgress() }, 10 * 1000) function updateProgress() { countEachRarity() countProgressOfEachPetal() document.getElementById('petalCounter_json').innerHTML = syntaxHighlight(JSON.stringify(lcs_, null, 4)) } function getToggleKey() { let t = '' for (const [key, value] of Object.entries(lcs_.toggleKey)) { if (value == true) t += ' ' + key if (key == 'key' && value[1] == 0) t += ` ${value[0].toUpperCase()} (${lcs_.toggleKey.code})` } return t } const container = document.getElementById('__skContainer') || new ElementCreate('div') .attr({ id: '__skContainer' }) .style({ margin: 0, position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%) scale(1)', width: '80%', height: '80%', zIndex: '999', transition: 'all 0.4s ease-in-out', fontFamily: 'Consolas, "Courier New", monospace', color: 'white', cursor: 'default', wordWrap: 'break-word' }) .append(document.body) .get() const container_petalCounter = new ElementCreate('div') .style({ backgroundColor: color.background, borderRadius: '10px', boxShadow: '5px 5px rgba(0, 0, 0, 0.3)', display: 'flex', width: '100%', height: '100%', }) .append(container) .get(); new ElementCreate('div') .style({ width: '70%', padding: '30px', fontSize: '14px', lineHeight: '14px', overflow: 'hidden auto', whiteSpace: 'nowrap' }) .content(`
Toggle key:
${getToggleKey()}
Exact number:
${syntaxHighlight(JSON.stringify(lcs_, null, 4))}`) .append(container_petalCounter) .get(); function buttonToggle(element, condition) { condition = condition == true ? false : true element.innerHTML = `${condition}` return condition } if (lcs_.exactNumber) document.getElementById(`petalCounter_exactNumber`).innerHTML = lcs_.exactNumber.toString().fontcolor(color.secondary) else document.getElementById(`petalCounter_exactNumber`).innerHTML = lcs_.exactNumber.toString().fontcolor(color.tertiary) document.getElementById('petalCounter_exactNumber').onclick = function () { lcs_.exactNumber = buttonToggle(this, lcs_.exactNumber) syncLcs() updateProgress() } document.getElementById('petalCounter_toggleKey').onclick = function () { if (isKeyPressed.toggleKey) { isKeyPressed.toggleKey = false this.innerHTML = 'Press a key'.toString().fontcolor(color.tertiary) } else { isKeyPressed.toggleKey = true this.innerHTML = getToggleKey().toString().fontcolor(color.primary) } } function countEachRarity() { let a = '', b = new Array(kRarity.length).fill(0) for (let i = inventoryBaseAddress; i < inventoryBaseAddress + kPetals?.sid?.length * kRarity.length; i += kRarity.length) { for (let j = 0; j < kRarity.length; j++) { b[j] += module[i + j] } } a += `
Press ${document.querySelector('#petalCounter_toggleKey > font').innerHTML} to open/close this menu.
1. In the Progress category, choose at least 1 for each rarity and petal.
After that, click Add button to add the petals you chose into script.
2. Click on the line of that petal's rarity to modify its Aim number (set to 0 to remove it from script).
Or you can click on that petal's image to modify all rarities' aims at once.
Script is created by Furaken (discord: samerkizi).
Github: https://github.com/Furaken.
Discord: https://discord.gg/tmWUfg4FR9.
Special thanks to Max Nest for auto inventoryBaseAddress finder.
Images by M28.
Removed smooth scrolling effect.
Reworked menu UI.
Some features were removed.
Script can find inventoryBaseAddress finder automatically (Credit to Max Nest).
The container now has smooth scrolling effect (Credit to Manuel Otto).
Added multiple petals counter.
Added Auto update ID (this requires you to use Find & Apply at least one times).
Added 3 new petals.
Added a manual way to find Basic ID: Find & Apply (Credit to Max Nest).
The container is now moveable and scalable.
Press = key to show/hide the container, this is also available in earlier versions. You can custom it in settings now.
`) .append(container_petalCounter) .get() document.documentElement.addEventListener('keydown', function (e) { if (!isKeyPressed.toggleKey) { lcs_.toggleKey = { Ctrl: e.ctrlKey, Shift: e.shiftKey, Alt: e.altKey, Meta: e.metaKey, key: [e.key, e.location], code: e.code, } } else if (e.key == lcs_.toggleKey.key[0] && e.ctrlKey == lcs_.toggleKey.Ctrl && e.shiftKey == lcs_.toggleKey.Shift && e.altKey == lcs_.toggleKey.Alt && e.metaKey == lcs_.toggleKey.Meta && !e.repeat) { container.style.transform = container.style.transform == 'translate(-50%, -50%) scale(1)' ? 'translate(-50%, -50%) scale(0)' : 'translate(-50%, -50%) scale(1)' } }) document.documentElement.addEventListener('keyup', function (e) { if (!isKeyPressed.toggleKey) { isKeyPressed.toggleKey = true document.getElementById('petalCounter_toggleKey').innerHTML = getToggleKey().toString().fontcolor(color.primary) document.querySelector('#petalCounter_message > font').innerHTML = document.getElementById('petalCounter_toggleKey').innerHTML syncLcs() } }) new ElementCreate('style').content(` p { margin: 3px; } h1 { line-height: 22px; } font { font-weight: bold; } .hover { transition: all 0.2s ease-in-out; } .hover:hover { background-color: rgba(255, 255, 255, 0.05); border-radius: 5px; padding: 5px; } .button { background-color: ${color.secondary}99; width: fit-content; border-radius: 5px; padding: 7px 12px; cursor: pointer; font-weight: bold; } .selected { background-color: ${color.secondary}33!important; border-radius: 5px; } ::-webkit-scrollbar { width: 5px; height: 5px; } ::-webkit-scrollbar-track { background: #00000000; } ::-webkit-scrollbar-thumb { background: #444; border-radius: 5px; } ::-webkit-scrollbar-thumb:hover { background: #444; } `).append('head') })();