Sigmally Fixes

Many necessary improvements for Sigmally and SigMod

目前為 2024-01-07 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Sigmally Fixes
// @namespace    https://8y8x.dev/sigmally-fixes
// @version      2024-01-07
// @description  Many necessary improvements for Sigmally and SigMod
// @author       8y8x
// @match        https://sigmally.com/
// @icon         https://8y8x.dev/favicon.ico
// @license      MIT
// @grant        none
// ==/UserScript==
 
'use strict';
 
async function wait(ms) {
	return new Promise(r => setTimeout(r, ms));
}
 
// Cell improvements
(async function() {
    const { options, settings } = await import('./assets/mjs/settings.mjs');
 
	// search for Cell prototype
	let CellProto;
	do {
		const allCells = options.cells.list;
		if (allCells.length === 0) {
			await wait(50);
		} else {
			CellProto = Object.getPrototypeOf(allCells[0]);
		}
	} while (!CellProto);
 
	// prevent setting transparency while drawing cells
	// gets rid of a lot of lag when big
	let drawingCell = false;
	const globalAlphaDescriptor = Object.getOwnPropertyDescriptor(CanvasRenderingContext2D.prototype, 'globalAlpha');
	Object.defineProperty(CanvasRenderingContext2D.prototype, 'globalAlpha', {
		set: function(x) {
			if (drawingCell) return;
			globalAlphaDescriptor.set.call(this, x);
		}
	})
 
	// don't call ctx.save() and ctx.restore(), saves a bit per frame
	// may cause problems in the future if rendering changes, but unlikely
	CellProto.draw = function(ctx) {
		ctx.globalAlpha = 1;
		drawingCell = true;
		try {
			this.drawShape(ctx);
			this.drawText(ctx);
		} finally {
			drawingCell = false;
		}
	}
 
    // outline cell if it can't split
	const oldDrawShape = CellProto.drawShape;
	CellProto.drawShape = function(ctx) {
		const { byId, mine } = options.cells;
		const idx = mine.indexOf(this.id);
		if (idx === -1)
			return oldDrawShape.call(this, ctx);
 
		if (this.ns >= 128) {
			// mass >= 163
			let nextId = options.cells.mine.length;
			for (let i = 0; i < idx; ++i) {
				const cell = byId[mine[i]];
				if (cell.ns >= 128) // mass >= 163
					++nextId;
			}
 
			if (nextId < 16)
				return oldDrawShape.call(this, ctx);
		}
 
		const realSColor = this.sColor;
		this.sColor = settings.gameSettings.darkTheme ? '#fff' : '#000';
		oldDrawShape.call(this, ctx);
		this.sColor = realSColor;
	}
 
	// sidestep text caching altogether - which significantly drags game performance -
	// and just draw the damn text
	// this mostly gets rid of lag spikes when switching tabs, especially when multiboxing
	function drawText(ctx, x, y, size, text, isSub) {
		ctx.save();
		ctx.font = size + 'px Ubuntu';
		ctx.textBaseline = 'middle';
		ctx.textAlign = 'center';
		if (isSub) {
			ctx.fillStyle = '#f9bf0d';
			ctx.strokeStyle = '#40200a';
		} else {
			ctx.fillStyle = '#fff';
			ctx.strokeStyle = '#000';
		}
 
		// note: game uses floor, i use ceil and +1
		ctx.lineWidth = Math.ceil(size / 10) + 1;
		if (ctx.lineWidth * options.camera.scale > 1)
			ctx.strokeText(text, x, y);
		ctx.fillText(text, x, y);
		ctx.restore();
	}
 
	// always draw mass text, avoid text caching
	CellProto.drawText = function(ctx) {
		if (this.isPellet || this.isJagged) return;
		let y = this.y;
		if (this.name && settings.gameSettings.showNames) {
			drawText(ctx, this.x, this.y, this.drawNameSize, this.name, this.isSub);
			y += Math.max(this.s / 4.5, this.nameSize / 1.5);
		}
 
		if (settings.gameSettings.showMass) {
			const mass = Math.floor(this.s * this.s / 100).toString();
			if (mass > 50)
				drawText(ctx, this.x, y, this.drawNameSize / 2, mass, this.isSub);
		}
	}
})();
 
// Networking improvements
(async function() {
    const { options } = await import('./assets/mjs/settings.mjs');
    const { C, sendMouseMove } = await import('./assets/mjs/ws.mjs');
 
	let lastW = performance.now();
 
	const nativeSend = WebSocket.prototype.send;
	WebSocket.prototype.send = function(buf) {
		let dv;
		if (buf instanceof Uint8Array) {
			dv = new DataView(buf.buffer);
		} else if (buf instanceof ArrayBuffer) {
			dv = new DataView(buf);
		} else {
			nativeSend.call(this, buf);
			return;
		}
 
 
		if (dv.getUint8(0) === C[17]) { // space key / split
			const { mainCanvas } = options.gameOptions;
			// before splitting, always send your exact mouse position.
			// splits are sent immediately, but mouse movements are normally not. so, you might split before
			// the server knows where you aimed.
			setTimeout(() => { // timeout to allow mouse events to fire
				// copy+pasted from source
				sendMouseMove(
					(options.gameOptions.mouseX - mainCanvas.width / 2) /
						options.camera.scale +
						options.camera.x,
					(options.gameOptions.mouseY - mainCanvas.height / 2) /
						options.camera.scale +
						options.camera.y
				);
 
				nativeSend.call(this, buf);
			});
			return;
		}
 
		if (dv.getUint8(0) === C[21]) { // W
			// SigMod for whatever reason sends ~300 W's per second when only
			// 25/s are ever registered. we ratelimit most of these sends.
			if (performance.now() - lastW < 30) return;
			lastW = performance.now();
		}
 
		nativeSend.call(this, buf);
	}
})();
 
// Lag improvements
(async function() {
    // note: no idea if this actually does anything lol
    const oldRequestAnimationFrame = requestAnimationFrame;
    window.requestAnimationFrame = function(fn) {
        if (document.visibilityState === 'hidden') // if tab is not visible (ctrl+w'd away or minimized)
            oldRequestAnimationFrame(() => requestAnimationFrame(fn)); // try rendering again next frame
        else
            oldRequestAnimationFrame(fn);
    }
})();
 
// Input improvements
(async function() {
	const { C, wsSend } = await import('./assets/mjs/ws.mjs');

	// create a dialog when closing a tab, in case of an accident
	addEventListener('beforeunload', e => {
	    e.preventDefault();
	    e.returnValue = '';
	});
 
	// disable ctrl+w (only when in fullscreen) - helps when multiboxing quickly
	document.addEventListener('keydown', e => {
	    const code = e.which || e.keyCode;
	    if (e.ctrlKey && code === 87) { // ctrl+w
	        e.preventDefault();
	    } else if (e.ctrlKey && code === 9) { // ctrl+tab
	        e.returnValue = true; // undo e.preventDefault()
	        e.stopImmediatePropagation(); // prevent sigmod from calling e.preventDefault() afterwards
	    }
	});
 
	// sometimes, splits won't go through. happens if you hold Space while switching tabs, which can happen
	// often if multiboxing quickly.
	addEventListener('blur', () => {
		const ev = new KeyboardEvent('keyup', { key: ' ', code: 'Space' });
		dispatchEvent(ev);
	});

	// send the release of the Q key, which allows you to switch spectate modes
	document.addEventListener('keyup', e => {
		if (e.key === 'q') {
			wsSend(new Uint8Array([ C[19] ]));
		}
	});
})();
 
(async function() {
	// SigMod-specific fixes
 
	// forcefully enable "remove outlines" (a SigMod feature) when big,
	// as it's ugly AND massively impacts performance.
	setInterval(() => {
		const checkbox = document.querySelector('#fps-remOutlines');
		if (!checkbox) return;
 
		if (!checkbox.checked) {
			checkbox.click(); // enable the checkbox
			checkbox.disabled = true;
		}
	}, 500);
})();