Greasy Fork is available in English.
The #1 Bloxd.io Client - FPS Boost, Widgets, Features & More. Client Updated!
// ==UserScript==
// @name Null Client
// @namespace null.client
// @version 1.3
// @description The #1 Bloxd.io Client - FPS Boost, Widgets, Features & More. Client Updated!
// @author Nullscape
// @match https://bloxd.io/*
// @match https://*.bloxd.io/*
// @match https://now.gg/*bloxd*
// @match https://www.crazygames.com/game/bloxdhop-io/*
// @icon https://i.postimg.cc/xjzYxS0R/Null-Client.png
// @license CC-BY-4.0
// @grant none
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
if (window.NullClientLoaded) return;
window.NullClientLoaded = true;
// Tab title manager
setInterval(() => {
if (document.hasFocus()) {
document.title = "🔥Null Client";
} else {
document.title = "👋Come back to Bloxd!";
}
}, 1000);
const CONFIG = {
VERSION: "1.3",
STORAGE: "null_client_v13",
THEME: {
DARK: "#1a1a1a",
GRAY: "#2a2a2a",
LIGHT: "#3a3a3a",
LIGHTER: "#4a4a4a",
BORDER: "#555555",
TEXT: "#fff",
DIM: "#999"
}
};
const DEFAULT_STATE = {
widgets: {
fps: false, cps: false, ping: false, coords: false,
speed: false, keystrokes: false, minimap: false, reach: false
},
positions: {
fps: {top:10,left:10}, cps: {top:40,left:10}, ping: {top:70,left:10},
coords: {top:100,left:10}, speed: {top:130,left:10},
keystrokes: {bottom:10,left:10}, minimap: {top:10,right:10}, reach: {top:160,left:10}
},
features: {
fullbright: false, zoom: false, sprint: false, bhop: false,
fastPlace: false, invCleaner: false, noShake: false,
hitParticles: false, smoothCamera: false, crosshair: false, reach: false
},
performance: {
particles: false, shadows: false, quality: false,
render: false, draw: false, boost: false
},
shaders: { enabled: false, type: 'morning' },
crosshair: { style: 'default', size: 10, thickness: 2, gap: 5, color: "#fff", opacity: 0.8 },
quality: { resolution: '1920x1080' },
superRank: { enabled: false, cape: null, nametagColor: "#fff" },
fov: 90,
fastPlaceDelay: 30,
reachDist: 4.5,
locked: true,
keystrokesMode: "wasd"
};
class State {
constructor() {
this.data = this.load();
}
load() {
try {
const saved = localStorage.getItem(CONFIG.STORAGE);
return saved ? {...DEFAULT_STATE, ...JSON.parse(saved)} : {...DEFAULT_STATE};
} catch(e) {
return {...DEFAULT_STATE};
}
}
save() {
try {
localStorage.setItem(CONFIG.STORAGE, JSON.stringify(this.data));
} catch(e) {}
}
get(path) {
const keys = path.split('.');
let val = this.data;
for(const k of keys) val = val?.[k];
return val;
}
set(path, value) {
const keys = path.split('.');
const last = keys.pop();
let target = this.data;
for(const k of keys) target = target[k] = target[k] || {};
target[last] = value;
this.save();
}
}
class Game {
constructor() {
this.camera = null;
this.player = null;
this.renderer = null;
this.scene = null;
this.THREE = null;
this.posHist = [];
this.velHist = [];
this.init();
}
init() {
setInterval(() => {
if(typeof window.THREE !== 'undefined' && !this.THREE) {
this.THREE = window.THREE;
this.hookCamera();
}
}, 100);
}
hookCamera() {
if(!this.THREE?.PerspectiveCamera) return;
const orig = this.THREE.PerspectiveCamera.prototype.updateProjectionMatrix;
this.THREE.PerspectiveCamera.prototype.updateProjectionMatrix = function() {
window.nullGame.camera = this;
return orig.call(this);
};
}
getCamera() {
return this.camera || window.camera || window.game?.camera;
}
getPlayer() {
return window.player || window.game?.player || window.localPlayer;
}
getRenderer() {
return this.renderer || window.renderer || window.game?.renderer;
}
getScene() {
return this.scene || window.scene || window.game?.scene;
}
getPos() {
const p = this.getPlayer();
if(!p) return {x:0,y:0,z:0};
const pos = p.position || p.body?.position;
if(pos) {
const curr = {x:pos.x||0, y:pos.y||0, z:pos.z||0, t:Date.now()};
this.posHist.push(curr);
if(this.posHist.length>30) this.posHist.shift();
return curr;
}
return this.posHist[this.posHist.length-1] || {x:0,y:0,z:0};
}
getVel() {
const p = this.getPlayer();
if(p) {
const vel = p.velocity || p.body?.velocity;
if(vel && (vel.x||vel.y||vel.z)) {
const curr = {x:vel.x||0, y:vel.y||0, z:vel.z||0, t:Date.now()};
this.velHist.push(curr);
if(this.velHist.length>20) this.velHist.shift();
return curr;
}
}
if(this.posHist.length>=2) {
const a = this.posHist[this.posHist.length-1];
const b = this.posHist[this.posHist.length-2];
const dt = (a.t-b.t)/1000;
if(dt>0 && dt<0.5) {
return {x:(a.x-b.x)/dt, y:(a.y-b.y)/dt, z:(a.z-b.z)/dt};
}
}
return {x:0,y:0,z:0};
}
getRot() {
const p = this.getPlayer();
return p?.rotation?.y || p?.rotation || 0;
}
hasBlock() {
const p = this.getPlayer();
if(!p) return false;
return !!(p.targetBlock || p.target || p.lookingAt);
}
getFOV() {
const c = this.getCamera();
return c?.fov || 90;
}
setFOV(fov) {
const c = this.getCamera();
if(c?.fov !== undefined) {
c.fov = fov;
c.updateProjectionMatrix?.();
return true;
}
return false;
}
getDist() {
const p = this.getPlayer();
if(!p) return null;
const pos = p.position || p.body?.position;
if(!pos) return null;
const t = p.targetBlock?.position;
if(t) {
return Math.sqrt((t.x-pos.x)**2+(t.y-pos.y)**2+(t.z-pos.z)**2);
}
return null;
}
}
class Anim {
constructor() {
this.cbs = new Map();
this.running = false;
this.last = 0;
}
add(name, cb) {
this.cbs.set(name, cb);
if(!this.running) this.start();
}
remove(name) {
this.cbs.delete(name);
if(this.cbs.size === 0) this.stop();
}
start() {
this.running = true;
this.last = performance.now();
this.loop();
}
stop() {
this.running = false;
}
loop() {
if(!this.running) return;
const now = performance.now();
const delta = now - this.last;
this.last = now;
this.cbs.forEach((cb,name) => {
try { cb(now, delta); } catch(e) {}
});
requestAnimationFrame(() => this.loop());
}
}
class Perf {
constructor(game) {
this.game = game;
this.on = false;
this.orig = {};
}
enable(opts) {
if(this.on) this.disable();
this.on = true;
if(opts.particles) this.doParticles();
if(opts.shadows) this.doShadows();
if(opts.quality) this.doQuality();
if(opts.render) this.doRender();
if(opts.draw) this.doDraw();
if(opts.boost) this.doBoost();
}
disable() {
if(!this.on) return;
this.on = false;
this.restore();
}
doParticles() {
document.querySelectorAll('[class*="particle"]').forEach(el => el.style.display='none');
const s = this.game.getScene();
if(s?.children) {
s.children.forEach(c => {
if(c.type==='ParticleSystem'||c.isPoints) c.visible=false;
});
}
}
doShadows() {
const r = this.game.getRenderer();
if(r?.shadowMap) {
this.orig.shadows = r.shadowMap.enabled;
r.shadowMap.enabled = false;
}
const s = this.game.getScene();
if(s?.traverse) {
s.traverse(c => {
if(c.castShadow!==undefined) c.castShadow=false;
if(c.receiveShadow!==undefined) c.receiveShadow=false;
});
}
}
doQuality() {
const r = this.game.getRenderer();
if(r?.setPixelRatio) {
this.orig.pixelRatio = r.getPixelRatio?.() || 1;
r.setPixelRatio(0.5);
}
}
doRender() {
const r = this.game.getRenderer();
if(r) {
if(r.sortObjects!==undefined) {
this.orig.sortObjects = r.sortObjects;
r.sortObjects = false;
}
}
}
doDraw() {
const s = this.game.getScene();
if(s?.children) {
s.children.forEach(c => {
if(c.type==='EffectComposer') {
if(c.enabled!==undefined) {
this.orig[`fx_${c.uuid}`] = c.enabled;
c.enabled = false;
}
}
});
}
}
doBoost() {
const r = this.game.getRenderer();
if(r?.context) {
const gl = r.context;
gl.hint(gl.FRAGMENT_SHADER_DERIVATIVE_HINT, gl.FASTEST);
gl.hint(gl.GENERATE_MIPMAP_HINT, gl.FASTEST);
}
}
restore() {
const r = this.game.getRenderer();
if(r) {
if(this.orig.shadows!==undefined && r.shadowMap) r.shadowMap.enabled=this.orig.shadows;
if(this.orig.pixelRatio!==undefined) r.setPixelRatio?.(this.orig.pixelRatio);
if(this.orig.sortObjects!==undefined) r.sortObjects=this.orig.sortObjects;
}
this.orig = {};
}
}
class Shader {
constructor(game, state) {
this.game = game;
this.state = state;
this.on = false;
this.el = null;
}
enable() {
if(this.on) return;
this.on = true;
const type = this.state.get('shaders.type');
if(type==='morning') this.morning();
else if(type==='sunset') this.sunset();
}
disable() {
if(!this.on) return;
this.on = false;
if(this.el) {
this.el.remove();
this.el = null;
}
}
morning() {
this.el = document.createElement('style');
this.el.textContent = 'canvas{filter:brightness(1.3)contrast(1.1)saturate(1.2)!important}';
document.head.appendChild(this.el);
}
sunset() {
this.el = document.createElement('style');
this.el.textContent = 'canvas{filter:brightness(1.2)contrast(1.15)saturate(1.3)sepia(0.2)!important}';
document.head.appendChild(this.el);
}
}
// Features
class BHOP {
constructor(game, anim) {
this.game = game;
this.anim = anim;
this.on = false;
this.lastJump = 0;
this.velBuf = [];
}
enable() {
if(this.on) return;
this.on = true;
this.anim.add('bhop', (now) => {
if(!this.on) return;
const v = this.game.getVel();
const speed = Math.sqrt(v.x**2+v.z**2);
if(speed<0.1) { this.velBuf=[]; return; }
if(now-this.lastJump<100) return;
this.velBuf.push(v.y);
if(this.velBuf.length>5) this.velBuf.shift();
if(this.velBuf.length<3) return;
const falling = v.y<0;
const near = Math.abs(v.y)<0.05;
const dec = this.velBuf.every((vel,i,arr)=>i===0||Math.abs(vel)<=Math.abs(arr[i-1]));
if(falling && dec && near) {
const p = this.game.getPlayer();
if(p?.jump) p.jump();
this.lastJump = now;
this.velBuf = [];
}
});
}
disable() {
if(!this.on) return;
this.on = false;
this.anim.remove('bhop');
this.velBuf = [];
}
}
class FastPlace {
constructor(game, anim) {
this.game = game;
this.anim = anim;
this.on = false;
this.down = false;
this.last = 0;
this.delay = 30;
}
enable(delay=30) {
if(this.on) return;
this.on = true;
this.delay = delay;
document.addEventListener('mousedown', this.handleDown=e=>{ if(e.button===2)this.down=true; });
document.addEventListener('mouseup', this.handleUp=e=>{ if(e.button===2)this.down=false; });
this.anim.add('fastplace', (now) => {
if(!this.on||!this.down) return;
if(now-this.last<this.delay) return;
if(!this.game.hasBlock()) return;
const c = document.querySelector('canvas');
if(c) {
c.dispatchEvent(new MouseEvent('mousedown',{bubbles:true,button:2}));
setTimeout(()=>c.dispatchEvent(new MouseEvent('mouseup',{bubbles:true,button:2})),10);
}
this.last = now;
});
}
disable() {
if(!this.on) return;
this.on = false;
document.removeEventListener('mousedown', this.handleDown);
document.removeEventListener('mouseup', this.handleUp);
this.anim.remove('fastplace');
}
}
class Sprint {
constructor(game) {
this.game = game;
this.on = false;
this.keys = new Set();
}
enable() {
if(this.on) return;
this.on = true;
document.addEventListener('keydown', this.handleDown=e=>{
const codes = ['KeyW','KeyA','KeyS','KeyD','ArrowUp','ArrowLeft','ArrowDown','ArrowRight'];
if(codes.includes(e.code)) {
this.keys.add(e.code);
const p = this.game.getPlayer();
if(p && p.sprint!==undefined) p.sprint=true;
}
});
document.addEventListener('keyup', this.handleUp=e=>{
this.keys.delete(e.code);
if(this.keys.size===0) {
const p = this.game.getPlayer();
if(p && p.sprint!==undefined) p.sprint=false;
}
});
}
disable() {
if(!this.on) return;
this.on = false;
document.removeEventListener('keydown', this.handleDown);
document.removeEventListener('keyup', this.handleUp);
this.keys.clear();
}
}
class SmoothZoom {
constructor(game, state, anim) {
this.game = game;
this.state = state;
this.anim = anim;
this.on = false;
this.current = 1;
this.target = 1;
}
enable() {
if(this.on) return;
this.on = true;
const fov = this.game.getFOV();
this.state.set('fov', fov);
document.addEventListener('keydown', this.handleDown=e=>{
if(e.repeat) return;
const map = {KeyV:2.0, KeyX:0.5, KeyC:1.5, KeyZ:0.75};
if(map[e.code]) {
e.preventDefault();
this.target = map[e.code];
}
});
document.addEventListener('keyup', this.handleUp=e=>{
if(['KeyV','KeyX','KeyC','KeyZ'].includes(e.code)) {
e.preventDefault();
this.target = 1.0;
}
});
this.anim.add('zoom', () => {
if(!this.on) return;
const diff = this.target - this.current;
if(Math.abs(diff)<0.001) {
this.current = this.target;
} else {
this.current += diff * 0.15;
}
const origFov = this.state.get('fov') || 90;
this.game.setFOV(origFov / this.current);
});
}
disable() {
if(!this.on) return;
this.on = false;
document.removeEventListener('keydown', this.handleDown);
document.removeEventListener('keyup', this.handleUp);
this.anim.remove('zoom');
const fov = this.state.get('fov');
if(fov) this.game.setFOV(fov);
}
}
class HitParticles {
constructor(anim) {
this.anim = anim;
this.on = false;
this.particles = [];
}
enable() {
if(this.on) return;
this.on = true;
this.anim.add('hitparticles', () => {
if(!this.on) return;
this.particles = this.particles.filter(p => {
const age = (Date.now()-p.created)/1000;
p.life = Math.max(0, 1-age*2);
if(p.life<=0) {
p.el.remove();
return false;
}
const left = parseFloat(p.el.style.left)||50;
const top = parseFloat(p.el.style.top)||50;
p.el.style.left = (left+p.vx)+'%';
p.el.style.top = (top+p.vy)+'%';
p.el.style.opacity = p.life;
p.vy += 0.1;
return true;
});
});
document.addEventListener('mousedown', this.handleDown=e=>{
if(!this.on||e.button!==0) return;
const p = document.createElement('div');
p.style.cssText='position:fixed;left:50%;top:50%;width:3px;height:3px;background:#fff;border-radius:50%;pointer-events:none;z-index:999999';
document.body.appendChild(p);
const angle = Math.random()*Math.PI*2;
const speed = 2+Math.random()*3;
this.particles.push({
el:p, vx:Math.cos(angle)*speed, vy:Math.sin(angle)*speed,
life:1, created:Date.now()
});
});
}
disable() {
if(!this.on) return;
this.on = false;
this.anim.remove('hitparticles');
document.removeEventListener('mousedown', this.handleDown);
this.particles.forEach(p=>p.el.remove());
this.particles = [];
}
}
class CustomCrosshair {
constructor(state) {
this.state = state;
this.on = false;
this.canvas = null;
}
enable() {
if(this.on) return;
this.on = true;
this.canvas = document.createElement('canvas');
this.canvas.style.cssText='position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:999999';
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
const ctx = this.canvas.getContext('2d');
document.body.appendChild(this.canvas);
const draw = () => {
const c = this.state.get('crosshair');
const cx = this.canvas.width/2;
const cy = this.canvas.height/2;
ctx.clearRect(0,0,this.canvas.width,this.canvas.height);
if(c.style==='circle') {
ctx.strokeStyle = c.color;
ctx.lineWidth = c.thickness;
ctx.globalAlpha = c.opacity;
ctx.beginPath();
ctx.arc(cx,cy,c.size,0,Math.PI*2);
ctx.stroke();
} else if(c.style==='dot') {
ctx.fillStyle = c.color;
ctx.globalAlpha = c.opacity;
ctx.beginPath();
ctx.arc(cx,cy,c.thickness,0,Math.PI*2);
ctx.fill();
} else {
ctx.strokeStyle = c.color;
ctx.lineWidth = c.thickness;
ctx.globalAlpha = c.opacity;
ctx.beginPath();
ctx.moveTo(cx-c.size-c.gap,cy);
ctx.lineTo(cx-c.gap,cy);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(cx+c.gap,cy);
ctx.lineTo(cx+c.size+c.gap,cy);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(cx,cy-c.size-c.gap);
ctx.lineTo(cx,cy-c.gap);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(cx,cy+c.gap);
ctx.lineTo(cx,cy+c.size+c.gap);
ctx.stroke();
}
};
draw();
window.addEventListener('resize', ()=>{
this.canvas.width=window.innerWidth;
this.canvas.height=window.innerHeight;
draw();
});
const s = document.createElement('style');
s.id='null-hide-cross';
s.textContent='.crosshair,#crosshair{display:none!important}';
document.head.appendChild(s);
}
disable() {
if(!this.on) return;
this.on = false;
if(this.canvas) this.canvas.remove();
const s = document.getElementById('null-hide-cross');
if(s) s.remove();
}
}
// Widgets
class Widgets {
constructor(state, game, anim) {
this.state = state;
this.game = game;
this.anim = anim;
this.widgets = new Map();
this.drag = null;
this.initDrag();
}
initDrag() {
document.addEventListener('mousemove', e => {
if(!this.drag) return;
const dx = e.clientX - this.drag.sx;
const dy = e.clientY - this.drag.sy;
if(!this.drag.dragging && Math.hypot(dx,dy)>5) this.drag.dragging=true;
if(this.drag.dragging) {
this.drag.el.style.left = Math.max(0,this.drag.ix+dx)+'px';
this.drag.el.style.top = Math.max(0,this.drag.iy+dy)+'px';
this.drag.el.style.right='auto';
this.drag.el.style.bottom='auto';
}
});
document.addEventListener('mouseup', () => {
if(!this.drag) return;
if(this.drag.dragging) {
const rect = this.drag.el.getBoundingClientRect();
this.state.set(`positions.${this.drag.key}`, {top:Math.round(rect.top),left:Math.round(rect.left)});
} else if(this.state.get('locked')) {
this.toggle(this.drag.key);
}
this.drag = null;
});
}
create(key, html, styles={}) {
this.destroy(key);
const w = document.createElement('div');
w.className = `null-widget null-${key}`;
w.innerHTML = html;
Object.assign(w.style, {
position:'fixed',
background:'rgba(26,26,26,0.95)',
border:'1px solid #555',
borderRadius:'6px',
padding:'8px',
fontSize:'13px',
fontFamily:'monospace',
color:'#fff',
zIndex:999999,
userSelect:'none',
cursor:this.state.get('locked')?'pointer':'move',
boxShadow:'0 2px 8px rgba(0,0,0,0.5)',
...styles
});
this.applyPos(w,key);
w.addEventListener('mousedown', e => {
if(e.button!==0) return;
const rect = w.getBoundingClientRect();
this.drag = {key,el:w,sx:e.clientX,sy:e.clientY,ix:rect.left,iy:rect.top,dragging:false};
});
document.body.appendChild(w);
this.widgets.set(key, w);
return w;
}
destroy(key) {
if(this.widgets.has(key)) {
this.widgets.get(key).remove();
this.widgets.delete(key);
}
this.anim.remove(key);
}
applyPos(el, key) {
const pos = this.state.get(`positions.${key}`);
if(!pos) return;
if(pos.top!==undefined) el.style.top=pos.top+'px';
if(pos.bottom!==undefined) el.style.bottom=pos.bottom+'px';
if(pos.left!==undefined) el.style.left=pos.left+'px';
if(pos.right!==undefined) el.style.right=pos.right+'px';
}
toggle(key) {
const cur = this.state.get(`widgets.${key}`);
this.state.set(`widgets.${key}`, !cur);
!cur ? this.show(key) : this.hide(key);
}
show(key) {
const handlers = {
fps: () => this.createFPS(),
cps: () => this.createCPS(),
ping: () => this.createPing(),
coords: () => this.createCoords(),
speed: () => this.createSpeed(),
keystrokes: () => this.createKeystrokes(),
minimap: () => this.createMinimap(),
reach: () => this.createReach()
};
if(handlers[key]) handlers[key]();
}
hide(key) {
this.destroy(key);
}
createFPS() {
this.create('fps', '60 FPS', {width:'85px',textAlign:'center',fontWeight:'bold'});
let frames=0, last=Date.now();
this.anim.add('fps', () => {
if(!this.widgets.has('fps')) return;
frames++;
const now = Date.now();
if(now>=last+1500) {
const fps = Math.round((frames*1000)/(now-last));
this.widgets.get('fps').textContent = `${Math.min(fps,999)} FPS`;
frames=0;
last=now;
}
});
}
createCPS() {
this.create('cps', '0 | 0 CPS', {width:'100px',textAlign:'center',fontWeight:'bold'});
let clicks = {l:[],r:[]};
const handler = e => {
const now = Date.now();
if(e.button===0) clicks.l.push(now);
else if(e.button===2) clicks.r.push(now);
};
document.addEventListener('mousedown', handler);
let lastUpdate = Date.now();
this.anim.add('cps', () => {
if(!this.widgets.has('cps')) {
document.removeEventListener('mousedown',handler);
return;
}
const now = Date.now();
if(now-lastUpdate>=1500) {
const cutoff = now-1000;
clicks.l = clicks.l.filter(t=>t>cutoff);
clicks.r = clicks.r.filter(t=>t>cutoff);
this.widgets.get('cps').textContent = `${clicks.l.length} | ${clicks.r.length} CPS`;
lastUpdate = now;
}
});
}
createPing() {
this.create('ping', '... ms', {width:'95px',textAlign:'center'});
const measure = async () => {
try {
const start = performance.now();
await fetch('/favicon.ico?_='+Date.now(),{method:'HEAD',cache:'no-store',mode:'no-cors'});
const ping = Math.round(performance.now()-start);
if(this.widgets.has('ping')) this.widgets.get('ping').textContent=`${ping} ms`;
} catch(e) {
if(this.widgets.has('ping')) this.widgets.get('ping').textContent='--- ms';
}
};
measure();
const interval = setInterval(()=>{
if(this.widgets.has('ping')) measure();
else clearInterval(interval);
}, 3000);
}
createCoords() {
this.create('coords', '<div style="opacity:0.6;font-size:10px;margin-bottom:3px">XYZ</div><div>0, 0, 0</div>', {width:'130px',textAlign:'center'});
let lastUpdate = Date.now();
this.anim.add('coords', () => {
if(!this.widgets.has('coords')) return;
const now = Date.now();
if(now-lastUpdate>=1500) {
const pos = this.game.getPos();
const el = this.widgets.get('coords').querySelector('div:last-child');
if(el) el.textContent=`${Math.round(pos.x)}, ${Math.round(pos.y)}, ${Math.round(pos.z)}`;
lastUpdate = now;
}
});
}
createSpeed() {
this.create('speed', '<div style="opacity:0.6;font-size:10px;margin-bottom:3px">SPEED</div><div>0.00 b/s</div>', {width:'105px',textAlign:'center'});
let lastUpdate = Date.now();
this.anim.add('speed', () => {
if(!this.widgets.has('speed')) return;
const now = Date.now();
if(now-lastUpdate>=1500) {
const vel = this.game.getVel();
const speed = Math.min(100, Math.sqrt(vel.x**2+vel.z**2));
const el = this.widgets.get('speed').querySelector('div:last-child');
if(el) el.textContent=`${speed.toFixed(2)} b/s`;
lastUpdate = now;
}
});
}
createKeystrokes() {
const mode = this.state.get('keystrokesMode');
const keys = mode==='wasd'?['W','A','S','D']:['↑','←','↓','→'];
const html = `
<div style="display:grid;grid-template-columns:repeat(3,32px);grid-template-rows:repeat(3,32px);gap:3px">
<div></div>
<div class="key" data-i="0" style="background:#1a1a1a;border:1px solid #555;border-radius:4px;display:flex;align-items:center;justify-content:center;font-weight:bold;color:#666;transition:all 0.1s">${keys[0]}</div>
<div></div>
<div class="key" data-i="1" style="background:#1a1a1a;border:1px solid #555;border-radius:4px;display:flex;align-items:center;justify-content:center;font-weight:bold;color:#666;transition:all 0.1s">${keys[1]}</div>
<div class="key" data-i="2" style="background:#1a1a1a;border:1px solid #555;border-radius:4px;display:flex;align-items:center;justify-content:center;font-weight:bold;color:#666;transition:all 0.1s">${keys[2]}</div>
<div class="key" data-i="3" style="background:#1a1a1a;border:1px solid #555;border-radius:4px;display:flex;align-items:center;justify-content:center;font-weight:bold;color:#666;transition:all 0.1s">${keys[3]}</div>
<div></div>
<div style="grid-column:2;display:flex;gap:4px;margin-top:4px">
<div class="mb" data-mb="LMB" style="flex:1;background:#1a1a1a;border:1px solid #555;border-radius:3px;padding:3px 0;text-align:center;font-size:9px;color:#666;transition:all 0.1s">L</div>
<div class="mb" data-mb="RMB" style="flex:1;background:#1a1a1a;border:1px solid #555;border-radius:3px;padding:3px 0;text-align:center;font-size:9px;color:#666;transition:all 0.1s">R</div>
</div>
<div></div>
</div>
`;
this.create('keystrokes', html, {padding:'6px',background:'rgba(10,10,10,0.98)'});
const w = this.widgets.get('keystrokes');
const keyMap = mode==='wasd'?['w','a','s','d']:['arrowup','arrowleft','arrowdown','arrowright'];
const onDown=e=>{
if(!this.widgets.has('keystrokes')) return;
const idx = keyMap.indexOf(e.key.toLowerCase());
if(idx!==-1) {
const k = w.querySelector(`[data-i="${idx}"]`);
if(k) {
k.style.background='#fff';
k.style.color='#000';
k.style.transform='scale(0.95)';
}
}
};
const onUp=e=>{
if(!this.widgets.has('keystrokes')) return;
const idx = keyMap.indexOf(e.key.toLowerCase());
if(idx!==-1) {
const k = w.querySelector(`[data-i="${idx}"]`);
if(k) {
k.style.background='#1a1a1a';
k.style.color='#666';
k.style.transform='scale(1)';
}
}
};
const onMDown=e=>{
if(!this.widgets.has('keystrokes')) return;
const btn = e.button===0?'l':e.button===2?'r':null;
if(btn) {
const m = w.querySelector(`[data-mb="${btn}"]`);
if(m) {
m.style.background='#fff';
m.style.color='#000';
}
}
};
const onMUp=e=>{
if(!this.widgets.has('keystrokes')) return;
const btn = e.button===0?'l':e.button===2?'r':null;
if(btn) {
const m = w.querySelector(`[data-mb="${btn}"]`);
if(m) {
m.style.background='#1a1a1a';
m.style.color='#666';
}
}
};
document.addEventListener('keydown', onDown);
document.addEventListener('keyup', onUp);
document.addEventListener('mousedown', onMDown);
document.addEventListener('mouseup', onMUp);
}
createMinimap() {
this.create('minimap', '<div style="font-size:9px;opacity:0.6;margin-bottom:4px">MAP</div><canvas id="minimap-canvas" width="130" height="130" style="border-radius:4px;border:1px solid #555"></canvas>', {width:'150px',padding:'8px',textAlign:'center'});
let last = 0;
this.anim.add('minimap', now => {
if(!this.widgets.has('minimap')) return;
if(now-last<50) return;
last = now;
const c = document.getElementById('minimap-canvas');
if(!c) return;
const ctx = c.getContext('2d');
const w=c.width, h=c.height;
ctx.fillStyle='#0a0a0a';
ctx.fillRect(0,0,w,h);
ctx.strokeStyle='#1a1a1a';
ctx.lineWidth=1;
for(let i=0;i<w;i+=20) {
ctx.beginPath();
ctx.moveTo(i,0);
ctx.lineTo(i,h);
ctx.stroke();
}
for(let i=0;i<h;i+=20) {
ctx.beginPath();
ctx.moveTo(0,i);
ctx.lineTo(w,i);
ctx.stroke();
}
const cx=w/2, cy=h/2, rot=this.game.getRot();
ctx.save();
ctx.translate(cx,cy);
ctx.rotate(rot);
ctx.fillStyle='#fff';
ctx.beginPath();
ctx.moveTo(0,-8);
ctx.lineTo(-5,5);
ctx.lineTo(5,5);
ctx.closePath();
ctx.fill();
ctx.restore();
});
}
createReach() {
this.create('reach', '<div style="opacity:0.6;font-size:10px;margin-bottom:3px">REACH</div><div>--- blocks</div>', {width:'110px',textAlign:'center'});
let lastUpdate = Date.now();
this.anim.add('reach', () => {
if(!this.widgets.has('reach')) return;
const now = Date.now();
if(now-lastUpdate>=1500) {
const dist = this.game.getDist();
const el = this.widgets.get('reach').querySelector('div:last-child');
if(el) el.textContent = dist!==null?`${dist.toFixed(2)} blocks`:'--- blocks';
lastUpdate = now;
}
});
}
}
// UI Manager
class UI {
constructor(client) {
this.client = client;
this.root = null;
this.visible = false;
this.menuBtn = null;
}
async create() {
await new Promise(r=>setTimeout(r,1000));
this.createMenuButton();
this.createMenu();
}
createMenuButton() {
this.menuBtn = document.createElement('div');
this.menuBtn.innerHTML = 'NULL';
this.menuBtn.style.cssText = `
position:fixed;top:10px;right:10px;
background:rgba(26,26,26,0.95);
color:#fff;padding:10px 15px;
border-radius:6px;cursor:pointer;
font-family:monospace;font-weight:bold;
font-size:12px;z-index:1000000;
border:1px solid #555;
box-shadow:0 2px 8px rgba(0,0,0,0.5);
user-select:none;
`;
this.menuBtn.addEventListener('click', () => this.toggle());
document.body.appendChild(this.menuBtn);
}
createMenu() {
this.root = document.createElement('div');
this.root.style.cssText = `position:fixed;inset:0;z-index:999999;display:none;background:rgba(0,0,0,0.8)`;
const t = CONFIG.THEME;
this.root.innerHTML = `
<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:750px;max-height:80vh;background:${t.GRAY};border:1px solid ${t.BORDER};border-radius:8px;overflow:hidden;display:flex;font-family:sans-serif;color:${t.TEXT};box-shadow:0 10px 40px rgba(0,0,0,0.9)">
<div style="width:160px;background:${t.DARK};padding:18px 12px;border-right:1px solid ${t.BORDER}">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:18px;padding-bottom:15px;border-bottom:1px solid ${t.BORDER}">
<div style="font-size:14px;font-weight:600">Null Client</div>
<div style="font-size:9px;opacity:0.5">v${CONFIG.VERSION}</div>
</div>
<div id="null-nav">
<button data-tab="widgets" class="active">Widgets</button>
<button data-tab="features">Features</button>
<button data-tab="performance">Performance</button>
<button data-tab="shaders">☀ Shaders</button>
<button data-tab="settings">Settings</button>
<button id="close-btn">Close</button>
</div>
</div>
<div style="flex:1;padding:22px;overflow-y:auto" id="null-content"></div>
</div>
`;
document.body.appendChild(this.root);
const style = document.createElement('style');
style.textContent = `
#null-nav button {
width:100%;padding:9px;background:transparent;border:none;
color:${t.DIM};text-align:left;border-radius:4px;cursor:pointer;
font-size:12px;margin-bottom:3px;transition:all 0.2s;
}
#null-nav button:hover { background:${t.LIGHT};color:${t.TEXT}; }
#null-nav button.active { background:${t.LIGHTER};color:${t.TEXT}; }
#null-content::-webkit-scrollbar { width:8px; }
#null-content::-webkit-scrollbar-thumb { background:${t.LIGHT};border-radius:4px; }
`;
document.head.appendChild(style);
this.setupHandlers();
this.switchTab('widgets');
}
setupHandlers() {
document.getElementById('close-btn').onclick = () => this.hide();
document.querySelectorAll('#null-nav button[data-tab]').forEach(btn => {
btn.onclick = () => {
document.querySelectorAll('#null-nav button').forEach(b=>b.classList.remove('active'));
btn.classList.add('active');
this.switchTab(btn.dataset.tab);
};
});
document.addEventListener('keydown', e => {
if(e.code==='ShiftRight') {
e.preventDefault();
this.toggle();
}
});
}
show() {
if(this.root) {
this.root.style.display='block';
this.visible=true;
}
}
hide() {
if(this.root) {
this.root.style.display='none';
this.visible=false;
}
}
toggle() {
this.visible ? this.hide() : this.show();
}
switchTab(tab) {
const tabs = {
widgets: () => this.renderWidgets(),
features: () => this.renderFeatures(),
performance: () => this.renderPerformance(),
shaders: () => this.renderShaders(),
settings: () => this.renderSettings()
};
if(tabs[tab]) tabs[tab]();
}
renderWidgets() {
const c = document.getElementById('null-content');
c.innerHTML = '<h2 style="margin-bottom:15px">Widgets</h2><div id="wg" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:12px"></div>';
const g = document.getElementById('wg');
const widgets = {fps:'FPS',cps:'CPS',ping:'Ping',coords:'Coords',speed:'Speed',keystrokes:'Keystrokes',minimap:'Minimap',reach:'Reach'};
Object.entries(widgets).forEach(([k,t]) => {
const on = this.client.state.get(`widgets.${k}`);
g.innerHTML += `
<div style="background:${CONFIG.THEME.DARK};border:1px solid ${CONFIG.THEME.BORDER};border-radius:6px;padding:14px">
<div style="font-size:12px;font-weight:600;margin-bottom:5px">${t}</div>
<div style="font-size:10px;opacity:0.5;margin-bottom:10px">Toggle ${t.toLowerCase()}</div>
<div style="display:flex;justify-content:space-between;align-items:center">
<span style="font-size:10px">Enable</span>
<div class="sw ${on?'on':''}" data-w="${k}" style="width:36px;height:18px;border-radius:9px;background:${on?CONFIG.THEME.LIGHTER:CONFIG.THEME.DARKER};position:relative;cursor:pointer;transition:all 0.2s">
<div style="position:absolute;top:2px;left:${on?'20px':'2px'};width:14px;height:14px;border-radius:7px;background:#fff;transition:all 0.2s"></div>
</div>
</div>
</div>
`;
});
setTimeout(() => {
g.querySelectorAll('.sw').forEach(sw => {
sw.onclick = () => {
const k = sw.dataset.w;
const cur = this.client.state.get(`widgets.${k}`);
this.client.state.set(`widgets.${k}`, !cur);
!cur ? this.client.widgets.show(k) : this.client.widgets.hide(k);
this.renderWidgets();
};
});
},100);
}
renderFeatures() {
const c = document.getElementById('null-content');
c.innerHTML = '<h2 style="margin-bottom:15px">Features</h2><div id="fg" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:12px"></div>';
const g = document.getElementById('fg');
const features = {fullbright:'Fullbright',zoom:'Smooth Zoom',sprint:'Auto Sprint',bhop:'BHOP',fastPlace:'Fast Place',invCleaner:'Inv Cleaner',noShake:'No Camera Shake',hitParticles:'Hit Particles',smoothCamera:'Smooth Camera',crosshair:'Custom Crosshair',reach:'Reach Extender'};
Object.entries(features).forEach(([k,t]) => {
const on = this.client.state.get(`features.${k}`);
g.innerHTML += `
<div style="background:${CONFIG.THEME.DARK};border:1px solid ${CONFIG.THEME.BORDER};border-radius:6px;padding:14px">
<div style="font-size:12px;font-weight:600;margin-bottom:5px">${t}</div>
<div style="font-size:10px;opacity:0.5;margin-bottom:10px">Toggle ${t.toLowerCase()}</div>
<div style="display:flex;justify-content:space-between;align-items:center">
<span style="font-size:10px">Enable</span>
<div class="sw ${on?'on':''}" data-f="${k}" style="width:36px;height:18px;border-radius:9px;background:${on?CONFIG.THEME.LIGHTER:CONFIG.THEME.DARKER};position:relative;cursor:pointer;transition:all 0.2s">
<div style="position:absolute;top:2px;left:${on?'20px':'2px'};width:14px;height:14px;border-radius:7px;background:#fff;transition:all 0.2s"></div>
</div>
</div>
</div>
`;
});
g.innerHTML += `
<div style="background:${CONFIG.THEME.DARK};border:1px solid ${CONFIG.THEME.BORDER};border-radius:6px;padding:14px">
<div style="font-size:12px;font-weight:600;margin-bottom:5px">Crosshair Style</div>
<select id="cross-sel" style="width:100%;padding:7px;background:${CONFIG.THEME.LIGHT};border:1px solid ${CONFIG.THEME.BORDER};border-radius:4px;color:#fff;font-size:11px;margin-top:8px">
<option value="default">Cross (Default)</option>
<option value="circle">Circle</option>
<option value="dot">Dot</option>
</select>
</div>
<div style="background:${CONFIG.THEME.DARK};border:1px solid ${CONFIG.THEME.BORDER};border-radius:6px;padding:14px">
<div style="font-size:12px;font-weight:600;margin-bottom:5px">Quality</div>
<select id="qual-sel" style="width:100%;padding:7px;background:${CONFIG.THEME.LIGHT};border:1px solid ${CONFIG.THEME.BORDER};border-radius:4px;color:#fff;font-size:11px;margin-top:8px">
<option value="1920x1080">1080p (Full HD)</option>
<option value="2560x1440">2K (1440p)</option>
<option value="3200x1800">1800p</option>
<option value="3840x2160">4K (2160p)</option>
</select>
</div>
`;
setTimeout(() => {
g.querySelectorAll('.sw').forEach(sw => {
sw.onclick = () => {
const k = sw.dataset.f;
const cur = this.client.state.get(`features.${k}`);
this.client.state.set(`features.${k}`, !cur);
this.client.toggleFeature(k, !cur);
this.renderFeatures();
};
});
const crossSel = document.getElementById('cross-sel');
if(crossSel) {
crossSel.value = this.client.state.get('crosshair.style');
crossSel.onchange = () => {
this.client.state.set('crosshair.style', crossSel.value);
if(this.client.crosshair?.on) {
this.client.crosshair.disable();
this.client.crosshair.enable();
}
};
}
const qualSel = document.getElementById('qual-sel');
if(qualSel) {
qualSel.value = this.client.state.get('quality.resolution');
qualSel.onchange = () => {
this.client.state.set('quality.resolution', qualSel.value);
const [w,h] = qualSel.value.split('x').map(Number);
const canvas = document.querySelector('canvas');
if(canvas) {
canvas.style.width = w+'px';
canvas.style.height = h+'px';
}
};
}
},100);
}
renderPerformance() {
const c = document.getElementById('null-content');
c.innerHTML = '<h2 style="margin-bottom:15px">Performance (120-240 FPS Boost)</h2><div id="pg" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:12px"></div>';
const g = document.getElementById('pg');
const perf = {particles:'Reduce Particles',shadows:'Disable Shadows',quality:'Lower Quality',render:'Optimize Renderer',draw:'Reduce Draw',boost:'FPS Boost'};
Object.entries(perf).forEach(([k,t]) => {
const on = this.client.state.get(`performance.${k}`);
g.innerHTML += `
<div style="background:${CONFIG.THEME.DARK};border:1px solid ${CONFIG.THEME.BORDER};border-radius:6px;padding:14px">
<div style="font-size:12px;font-weight:600;margin-bottom:5px">${t}</div>
<div style="font-size:10px;opacity:0.5;margin-bottom:10px">Toggle ${t.toLowerCase()}</div>
<div style="display:flex;justify-content:space-between;align-items:center">
<span style="font-size:10px">Enable</span>
<div class="sw ${on?'on':''}" data-p="${k}" style="width:36px;height:18px;border-radius:9px;background:${on?CONFIG.THEME.LIGHTER:CONFIG.THEME.DARKER};position:relative;cursor:pointer;transition:all 0.2s">
<div style="position:absolute;top:2px;left:${on?'20px':'2px'};width:14px;height:14px;border-radius:7px;background:#fff;transition:all 0.2s"></div>
</div>
</div>
</div>
`;
});
setTimeout(() => {
g.querySelectorAll('.sw').forEach(sw => {
sw.onclick = () => {
const k = sw.dataset.p;
const cur = this.client.state.get(`performance.${k}`);
this.client.state.set(`performance.${k}`, !cur);
this.client.applyPerf();
this.renderPerformance();
};
});
},100);
}
renderShaders() {
const c = document.getElementById('null-content');
c.innerHTML = `
<h2 style="margin-bottom:5px">☀ Shaders</h2>
<div style="color:#f0ad4e;font-size:11px;margin-bottom:15px">⚠ WARNING: May reduce FPS and increase CPU usage</div>
<div id="sg" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:12px"></div>
`;
const g = document.getElementById('sg');
const on = this.client.state.get('shaders.enabled');
const type = this.client.state.get('shaders.type');
g.innerHTML += `
<div style="background:${CONFIG.THEME.DARK};border:1px solid ${CONFIG.THEME.BORDER};border-radius:6px;padding:14px">
<div style="font-size:12px;font-weight:600;margin-bottom:5px">Shaders</div>
<div style="font-size:10px;opacity:0.5;margin-bottom:10px">Enable shader system</div>
<div style="display:flex;justify-content:space-between;align-items:center">
<span style="font-size:10px">Enable</span>
<div id="shader-sw" class="sw ${on?'on':''}" style="width:36px;height:18px;border-radius:9px;background:${on?CONFIG.THEME.LIGHTER:CONFIG.THEME.DARKER};position:relative;cursor:pointer;transition:all 0.2s">
<div style="position:absolute;top:2px;left:${on?'20px':'2px'};width:14px;height:14px;border-radius:7px;background:#fff;transition:all 0.2s"></div>
</div>
</div>
</div>
<div style="background:${CONFIG.THEME.DARK};border:1px solid ${CONFIG.THEME.BORDER};border-radius:6px;padding:14px">
<div style="font-size:12px;font-weight:600;margin-bottom:5px">Shader Type</div>
<div style="font-size:10px;opacity:0.5;margin-bottom:10px">Select shader</div>
<select id="shader-type" style="width:100%;padding:7px;background:${CONFIG.THEME.LIGHT};border:1px solid ${CONFIG.THEME.BORDER};border-radius:4px;color:#fff;font-size:11px">
<option value="morning" ${type==='morning'?'selected':''}>Sunny Morning</option>
<option value="sunset" ${type==='sunset'?'selected':''}>Sunset</option>
</select>
</div>
`;
setTimeout(() => {
const sw = document.getElementById('shader-sw');
if(sw) {
sw.onclick = () => {
const cur = this.client.state.get('shaders.enabled');
this.client.state.set('shaders.enabled', !cur);
!cur ? this.client.shaders.enable() : this.client.shaders.disable();
this.renderShaders();
};
}
const sel = document.getElementById('shader-type');
if(sel) {
sel.onchange = () => {
this.client.state.set('shaders.type', sel.value);
if(this.client.shaders.on) {
this.client.shaders.disable();
this.client.shaders.enable();
}
};
}
},100);
}
renderSettings() {
const c = document.getElementById('null-content');
const locked = this.client.state.get('locked');
c.innerHTML = `
<h2 style="margin-bottom:15px">Settings</h2>
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:12px">
<div style="background:${CONFIG.THEME.DARK};border:1px solid ${CONFIG.THEME.BORDER};border-radius:6px;padding:14px">
<div style="font-size:12px;font-weight:600;margin-bottom:5px">Widget Lock</div>
<div style="font-size:10px;opacity:0.5;margin-bottom:10px">Lock widget positions</div>
<div style="display:flex;justify-content:space-between;align-items:center">
<span style="font-size:10px" id="lock-lbl">${locked?'Locked':'Unlocked'}</span>
<div id="lock-sw" class="sw ${locked?'on':''}" style="width:36px;height:18px;border-radius:9px;background:${locked?CONFIG.THEME.LIGHTER:CONFIG.THEME.DARKER};position:relative;cursor:pointer;transition:all 0.2s">
<div style="position:absolute;top:2px;left:${locked?'20px':'2px'};width:14px;height:14px;border-radius:7px;background:#fff;transition:all 0.2s"></div>
</div>
</div>
</div>
<div style="background:${CONFIG.THEME.DARK};border:1px solid ${CONFIG.THEME.BORDER};border-radius:6px;padding:14px">
<div style="font-size:12px;font-weight:600;margin-bottom:5px">FOV</div>
<div style="font-size:10px;opacity:0.5;margin-bottom:10px">Field of view (30-120)</div>
<input type="number" id="fov-inp" value="${this.client.state.get('fov')}" min="30" max="120" style="width:100%;padding:7px;background:${CONFIG.THEME.LIGHT};border:1px solid ${CONFIG.THEME.BORDER};border-radius:4px;color:#fff;font-size:11px;margin-top:5px">
<button id="fov-btn" style="width:100%;padding:7px;background:${CONFIG.THEME.LIGHTER};border:none;border-radius:4px;color:#fff;cursor:pointer;font-size:11px;margin-top:6px">Apply</button>
</div>
<div style="background:${CONFIG.THEME.DARK};border:1px solid ${CONFIG.THEME.BORDER};border-radius:6px;padding:14px;grid-column:1/-1">
<div style="font-size:12px;font-weight:600;margin-bottom:5px">Reset Settings</div>
<div style="font-size:10px;opacity:0.5;margin-bottom:10px">Reset all settings to default</div>
<button id="reset-btn" style="width:100%;padding:7px;background:${CONFIG.THEME.LIGHT};border:none;border-radius:4px;color:#fff;cursor:pointer;font-size:11px;margin-top:5px">Reset All</button>
</div>
</div>
`;
setTimeout(() => {
const lockSw = document.getElementById('lock-sw');
const lockLbl = document.getElementById('lock-lbl');
if(lockSw && lockLbl) {
lockSw.onclick = () => {
const cur = this.client.state.get('locked');
this.client.state.set('locked', !cur);
this.renderSettings();
};
}
const fovBtn = document.getElementById('fov-btn');
if(fovBtn) {
fovBtn.onclick = () => {
const v = parseInt(document.getElementById('fov-inp').value);
if(v>=30 && v<=120) {
this.client.state.set('fov', v);
this.client.game.setFOV(v);
}
};
}
const resetBtn = document.getElementById('reset-btn');
if(resetBtn) {
resetBtn.onclick = () => {
if(confirm('Reset all settings?')) {
localStorage.removeItem(CONFIG.STORAGE);
location.reload();
}
};
}
},100);
}
}
// Main Client
class NullClient {
constructor() {
this.state = new State();
this.game = new Game();
this.anim = new Anim();
this.perf = new Perf(this.game);
this.shaders = new Shader(this.game, this.state);
this.widgets = new Widgets(this.state, this.game, this.anim);
this.ui = new UI(this);
this.bhop = new BHOP(this.game, this.anim);
this.fastPlace = new FastPlace(this.game, this.anim);
this.sprint = new Sprint(this.game);
this.zoom = new SmoothZoom(this.game, this.state, this.anim);
this.hitParticles = new HitParticles(this.anim);
this.crosshair = new CustomCrosshair(this.state);
window.nullGame = this.game;
}
async init() {
await this.ui.create();
this.restore();
this.anim.start();
console.log('%c[Null Client] v1.3 Ready!', 'color:#5cb85c;font-weight:bold;font-size:14px');
}
restore() {
Object.keys(this.state.get('widgets')).forEach(k => {
if(this.state.get(`widgets.${k}`)) this.widgets.show(k);
});
const features = this.state.get('features');
Object.keys(features).forEach(k => {
if(features[k]) this.toggleFeature(k, true);
});
this.applyPerf();
if(this.state.get('shaders.enabled')) this.shaders.enable();
}
toggleFeature(k, on) {
const handlers = {
fullbright: o => this.applyFullbright(o),
zoom: o => o ? this.zoom.enable() : this.zoom.disable(),
sprint: o => o ? this.sprint.enable() : this.sprint.disable(),
bhop: o => o ? this.bhop.enable() : this.bhop.disable(),
fastPlace: o => o ? this.fastPlace.enable(this.state.get('fastPlaceDelay')) : this.fastPlace.disable(),
hitParticles: o => o ? this.hitParticles.enable() : this.hitParticles.disable(),
crosshair: o => o ? this.crosshair.enable() : this.crosshair.disable()
};
if(handlers[k]) handlers[k](on);
}
applyFullbright(on) {
const id = 'null-fullbright';
const ex = document.getElementById(id);
if(ex) ex.remove();
if(on) {
const s = document.createElement('style');
s.id = id;
s.textContent = 'canvas{filter:brightness(1.8)contrast(1.05)!important}';
document.head.appendChild(s);
}
}
applyPerf() {
const settings = this.state.get('performance');
const any = Object.values(settings).some(v => v);
any ? this.perf.enable(settings) : this.perf.disable();
}
}
// Initialize
const client = new NullClient();
setTimeout(() => client.init(), 1000);
window.NullClient = client;
})();