// ==UserScript==
// @name x-RedDragon Client
// @namespace http://youtube.com/@x-RedDragonYT
// @version 1.0.5
// @description R=InstaNormal, T=BoostInsta, G=BoostSpike, B=4Traps/Boost, M=AutoMills, F=Trap/BoostPad, V=Spike, N=Mill, H=Teleport/Turret, P=AutoGrindsBETA, AutoBiomeHat, Esc=MenuMusic, ClickRight=FastBreak, AutoGG, AutoHeal, AntiInsta and visual mods.
// @icon https://i.imgur.com/AFJt4iq.png
// @author x-RedDragonYT
// @match *://moomoo.io/*
// @match *://*.moomoo.io/*
// @grant none
// @require https://update.greasyfork.org/scripts/423602/1005014/msgpack.js
// @require https://update.greasyfork.org/scripts/480301/1322984/CowJS.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/fontfaceobserver.standalone.min.js
// @license MIT
// ==/UserScript==
(function () {
'use strict';
let ws;
const msgpack5 = window.msgpack;
let boostType, spikeType, turretType = null, windmillType = null, foodType;
let width, height, mouseX, mouseY;
let myPlayer = {
id: null, x: null, y: null, dir: null, object: null,
weapon: null, clan: null, isLeader: null,
hat: null, accessory: null, isSkull: null
};
let myPlayeroldx, myPlayeroldy;
let automillx = 10, automilly = 10;
let walkmillhaha = false;
let healToggle = true;
const keysPressed = {};
const placementIntervals = {};
let gInterval = null;
let prevKillCount = 0;
const ID_BullHelmet = 7;
const ID_TurretGear = 53;
const ID_SoldierHelmet = 6;
const ID_TankGear = 40;
const cvs = document.getElementById("gameCanvas");
if (!cvs) return;
cvs.addEventListener("mousemove", e => {
mouseX = e.clientX;
mouseY = e.clientY;
width = e.target.clientWidth;
height = e.target.clientHeight;
});
function emit(type, ...data) {
if (ws && msgpack5) ws.send(Uint8Array.from(msgpack5.encode([type, data])));
}
function doNewSend(sender) {
if (ws && msgpack5) ws.send(new Uint8Array(Array.from(msgpack5.encode(sender))));
}
function place(id, angle = Math.atan2(mouseY - height / 2, mouseX - width / 2)) {
if (id == null) return;
doNewSend(["z", [id, null]]);
doNewSend(["F", [1, angle]]);
doNewSend(["F", [0, angle]]);
doNewSend(["z", [myPlayer.weapon, true]]);
}
function isVisible(el) {
return el && el.offsetParent !== null;
}
function updateItems() {
for (let i = 31; i < 33; i++) if (isVisible(document.getElementById("actionBarItem" + i))) boostType = i - 16;
for (let i = 22; i < 26; i++) if (isVisible(document.getElementById("actionBarItem" + i))) spikeType = i - 16;
for (let i = 26; i <= 28; i++) if (isVisible(document.getElementById("actionBarItem" + i))) windmillType = i - 16;
for (let i = 33; i <= 38; i++) if (i !== 36 && isVisible(document.getElementById("actionBarItem" + i))) turretType = i - 16;
for (let i = 16; i <= 18; i++) if (isVisible(document.getElementById("actionBarItem" + i))) foodType = i - 16;
}
setInterval(updateItems, 250);
function toRad(degrees) {
return degrees * 0.01745329251;
}
function getSecondaryWeaponIndex() {
for (let i = 9; i <= 15; i++) {
if (isVisible(document.getElementById("actionBarItem" + i)) && i !== myPlayer.weapon) {
return i;
}
}
return myPlayer.weapon;
}
function sequenceInstaKill(includeBoost = false) {
if (includeBoost) place(boostType);
setTimeout(() => {
doNewSend(["c", [0, ID_BullHelmet, 0]]);
emit("z", myPlayer.weapon, true);
emit("F", 1);
emit("F", 0);
setTimeout(() => {
doNewSend(["c", [0, ID_TurretGear, 0]]);
const secondary = getSecondaryWeaponIndex();
emit("z", secondary, true);
emit("F", 1);
emit("F", 0);
setTimeout(() => {
doNewSend(["c", [0, ID_SoldierHelmet, 0]]);
}, 200);
}, 120);
}, 120);
}
function startPlacingStructure(key, itemId) {
if (!placementIntervals[key]) {
placementIntervals[key] = setInterval(() => place(itemId), 50);
}
}
function stopPlacingStructure(key) {
clearInterval(placementIntervals[key]);
delete placementIntervals[key];
}
function performGSequence() {
const dir = myPlayer.dir;
place(spikeType, dir + Math.PI / 2);
place(spikeType, dir - Math.PI / 2);
place(boostType, dir);
}
function performGReleaseSequence() {
const dir = myPlayer.dir;
place(spikeType, dir - Math.PI / 4);
place(spikeType, dir + Math.PI / 4);
}
function performBPlacement() {
const base = myPlayer.dir;
place(boostType, base);
place(boostType, base + Math.PI / 2);
place(boostType, base - Math.PI / 2);
place(boostType, base + Math.PI);
}
document.addEventListener("keydown", e => {
if (document.activeElement.id.toLowerCase() === 'chatbox') return;
const k = e.key.toLowerCase();
if (keysPressed[k]) return;
keysPressed[k] = true;
if (k === 'r') sequenceInstaKill(false);
if (k === 't') sequenceInstaKill(true);
if (k === 'm') {
walkmillhaha = !walkmillhaha;
doNewSend(["6", ["Mills : " + walkmillhaha]]);
}
if (k === 'f') startPlacingStructure(k, boostType);
if (k === 'v') startPlacingStructure(k, spikeType);
if (k === 'n') startPlacingStructure(k, windmillType);
if (k === 'h') startPlacingStructure(k, turretType);
if (k === 'g' && !gInterval) {
performGSequence();
gInterval = setInterval(performGSequence, 80);
}
if (k === 'b') {
performBPlacement();
}
});
document.addEventListener("keyup", e => {
const k = e.key.toLowerCase();
keysPressed[k] = false;
stopPlacingStructure(k);
if (k === 'g' && gInterval) {
clearInterval(gInterval);
gInterval = null;
setTimeout(performGReleaseSequence, 80);
}
});
// ✅ Redefinición segura de WebSocket.send
if (!WebSocket.prototype.__originalSend) {
WebSocket.prototype.__originalSend = WebSocket.prototype.send;
WebSocket.prototype.send = function (data) {
if (!ws) {
ws = this;
document.ws = this;
ws.addEventListener("message", handleMessage);
}
return this.__originalSend(data);
};
}
function handleMessage(m) {
let temp = msgpack5.decode(new Uint8Array(m.data));
let data = (temp.length > 1) ? [temp[0], ...temp[1]] : temp;
if (!data) return;
if (data[0] === "C" && myPlayer.id == null) {
myPlayer.id = data[1];
}
if (data[0] === "a") {
for (let i = 0; i < data[1].length / 13; i++) {
let obj = data[1].slice(13 * i, 13 * i + 13);
if (obj[0] === myPlayer.id) {
[myPlayer.x, myPlayer.y, myPlayer.dir, myPlayer.object, myPlayer.weapon,
, myPlayer.clan, myPlayer.isLeader, myPlayer.hat, myPlayer.accessory,
myPlayer.isSkull] = [obj[1], obj[2], obj[3], obj[4],
obj[5], obj[7], obj[8], obj[9], obj[10], obj[11]];
}
}
if (automillx === false) automillx = myPlayer.x;
if (automilly === false) automilly = myPlayer.y;
if (myPlayeroldy !== myPlayer.y || myPlayeroldx !== myPlayer.x) {
if (walkmillhaha) {
if (Math.sqrt(Math.pow(myPlayer.y - automilly, 2) + Math.pow(myPlayer.x - automillx, 2)) > 100) {
let angle = Math.atan2(myPlayeroldy - myPlayer.y, myPlayeroldx - myPlayer.x);
place(windmillType, angle + toRad(78));
place(windmillType, angle - toRad(78));
place(windmillType, angle);
doNewSend(["D", [Math.atan2(mouseY - height / 2, mouseX - width / 2)]]);
automillx = myPlayer.x;
automilly = myPlayer.y;
}
}
myPlayeroldx = myPlayer.x;
myPlayeroldy = myPlayer.y;
}
}
if (data[0] === 'O' && data[1] === myPlayer.id) {
const hp = data[2];
if (hp < 100 && hp > 0 && healToggle) {
let delay = hp <= 60 ? 5 : 122;
setTimeout(() => place(foodType), delay);
}
}
}
// 🧠 Anti-Rotación
Object.defineProperty(Object.prototype, "turnSpeed", {
get() { return 0; },
set(_) {},
configurable: true
});
// 🗨️ AutoGG al matar
const observeKills = new MutationObserver(mutations => {
for (const mutation of mutations) {
if (mutation.target.id === "killCounter") {
const count = parseInt(mutation.target.innerText, 10) || 0;
if (count > prevKillCount) {
prevKillCount = count;
doNewSend(["6", ["<[GG]>x-RedDragon Client<[GG]>"]]);
}
}
}
});
observeKills.observe(document, { subtree: true, childList: true });
// 🖱️ Click derecho: ataque automático + cambio de casco (sin storeEquip)
let rightClickAttackInterval = null;
let rightClickHeld = false;
let isPlacingStructure = false;
let attackPausedForPlacement = false;
function startAttackLoop() {
if (rightClickAttackInterval) return;
rightClickAttackInterval = setInterval(() => {
doNewSend(["c", [0, ID_TankGear, 0]]); // Casco ofensivo
doNewSend(["F", [1]]);
setTimeout(() => doNewSend(["F", [0]]), 10);
}, 60);
}
function stopAttackLoop() {
clearInterval(rightClickAttackInterval);
rightClickAttackInterval = null;
}
// Escucha el clic derecho y activa la lógica de ataque
document.addEventListener("mousedown", function (e) {
if (e.button === 2 && !rightClickHeld) {
rightClickHeld = true;
doNewSend(["c", [0, ID_TankGear, 0]]); // Equipar casco ofensivo
setTimeout(() => {
if (rightClickHeld && !isPlacingStructure) {
startAttackLoop();
}
}, 60);
}
});
document.addEventListener("mouseup", function (e) {
if (e.button === 2 && rightClickHeld) {
rightClickHeld = false;
stopAttackLoop();
doNewSend(["F", [0]]);
setTimeout(() => doNewSend(["c", [0, ID_SoldierHelmet, 0]]), 100); // Volver al casco base
}
});
// Interceptamos la función `place(...)` para pausar el ataque si se colocan estructuras
const originalPlace = place;
place = function (...args) {
isPlacingStructure = true;
if (rightClickAttackInterval) {
attackPausedForPlacement = true;
stopAttackLoop();
}
originalPlace.apply(this, args);
setTimeout(() => {
isPlacingStructure = false;
if (rightClickHeld && attackPausedForPlacement) {
attackPausedForPlacement = false;
startAttackLoop();
}
}, 100);
};
// 🔁 AutoGrind Beta-2
let autoGrindActive = false;
let autoGrindInterval = null;
const myStructures = [];
const STRUCTURE_LIFETIME = 1000;
const STRUCTURE_DISTANCE = 50;
const UP_DIR = -Math.PI / 2;
function customPlaceAndTrack(id, angle) {
place(id, angle);
const fakeX = myPlayer.x + Math.cos(angle) * STRUCTURE_DISTANCE;
const fakeY = myPlayer.y + Math.sin(angle) * STRUCTURE_DISTANCE;
myStructures.push({ type: id, angle, x: fakeX, y: fakeY, time: Date.now() });
}
function cleanupStructures() {
const now = Date.now();
while (myStructures.length > 0 && now - myStructures[0].time > STRUCTURE_LIFETIME) {
myStructures.shift();
}
}
function countRecentPlaced(type) {
const now = Date.now();
return myStructures.filter(obj =>
obj.type === type && now - obj.time < STRUCTURE_LIFETIME
).length;
}
document.addEventListener("keydown", e => {
if (document.activeElement.id.toLowerCase() === 'chatbox') return;
if (e.key.toLowerCase() === 'p') {
autoGrindActive = !autoGrindActive;
if (autoGrindActive) {
console.log("⚙️ AutoGrind ACTIVADO");
doNewSend(["6", ["AutoGrind ACTIVADO"]]);
// ⚔️ Equipar TankGear al iniciar
doNewSend(["c", [0, ID_TankGear, 0]]);
autoGrindInterval = setInterval(() => {
if (!turretType || typeof myPlayer.x !== 'number' || typeof myPlayer.y !== 'number') return;
cleanupStructures();
emit("D", UP_DIR);
if (countRecentPlaced(turretType) < 2) {
customPlaceAndTrack(turretType, UP_DIR);
}
doNewSend(["F", [1]]);
setTimeout(() => doNewSend(["F", [0]]), 10);
// ⚔️ Reequipar TankGear por seguridad
doNewSend(["c", [0, ID_TankGear, 0]]);
emit("D", UP_DIR);
}, 120);
} else {
console.log("❌ AutoGrind DESACTIVADO");
doNewSend(["6", ["AutoGrind DESACTIVADO"]]);
clearInterval(autoGrindInterval);
autoGrindInterval = null;
// 🛡 Volver a SoldierHelmet después
setTimeout(() => {
doNewSend(["c", [0, ID_SoldierHelmet, 0]]);
}, 300);
}
}
});
// 🎩 AutoBiomeHatController
function autoBiomeHatController() {
const ID_SoldierHelmet = 6;
const ID_SnowHelmet = 15;
const ID_SandHelmet = 31;
const ID_GrassHelmet = 12;
let normalHat = ID_GrassHelmet;
let currentHat = null;
let overridePause = false;
let resumeTimeout = null;
const movementKeys = new Set();
const overrideKeys = new Set(["r", "t", " "]);
function setHat(id) {
if (id !== currentHat && myPlayer && myPlayer.id != null) {
currentHat = id;
doNewSend(["c", [0, id, 0]]);
}
}
function updateBiomeHat() {
if (!myPlayer || typeof myPlayer.y !== "number") return;
if (myPlayer.y < 2400) normalHat = ID_SnowHelmet;
else if (myPlayer.y > 6850 && myPlayer.y < 7550) normalHat = ID_SandHelmet;
else normalHat = ID_GrassHelmet;
}
function updateHatLogic() {
if (overridePause) return;
updateBiomeHat();
if (movementKeys.size > 0) {
setHat(normalHat);
} else {
setHat(ID_SoldierHelmet);
}
}
function pauseOverride() {
overridePause = true;
if (resumeTimeout) clearTimeout(resumeTimeout);
}
function resumeOverride() {
if (resumeTimeout) clearTimeout(resumeTimeout);
resumeTimeout = setTimeout(() => {
overridePause = false;
updateHatLogic();
}, 360);
}
document.addEventListener("keydown", e => {
const key = e.key.toLowerCase();
if (["w", "a", "s", "d", "arrowup", "arrowdown", "arrowleft", "arrowright"].includes(key)) {
if (!movementKeys.has(key)) {
movementKeys.add(key);
updateHatLogic();
}
}
if (overrideKeys.has(key)) pauseOverride();
});
document.addEventListener("keyup", e => {
const key = e.key.toLowerCase();
if (movementKeys.delete(key)) updateHatLogic();
if (overrideKeys.has(key)) resumeOverride();
});
document.addEventListener("mousedown", e => {
if (e.button === 2) pauseOverride();
});
document.addEventListener("mouseup", e => {
if (e.button === 2) resumeOverride();
});
setInterval(() => {
if (!overridePause) updateHatLogic();
}, 250); // más reactivo y fluido
console.log("✅ AutoBiomeHatController V2 activado.");
}
autoBiomeHatController();
})();
(function() {
'use strict';
const GRID_ENABLED = false;
function waitForConfig(callback) {
if (window.config && window.config.maxScreenWidth && window.config.maxScreenHeight) {
callback();
} else {
setTimeout(() => waitForConfig(callback), 100);
}
}
waitForConfig(() => {
const maxWidth = window.config.maxScreenWidth;
const maxHeight = window.config.maxScreenHeight;
const CELL_SIZE = 50;
const tolerance = 1.5;
function isGridLinePair(x1, y1, x2, y2) {
// Solo bloqueamos líneas completamente horizontales o verticales
const isStraight = (x1 === x2 || y1 === y2);
// Solo si ambas coordenadas están cerca de múltiplos del grid
const isNearGrid = (coord) =>
(coord % CELL_SIZE <= tolerance) || (CELL_SIZE - (coord % CELL_SIZE) <= tolerance);
return isStraight && (isNearGrid(x1) || isNearGrid(y1));
}
let lastMoveTo = null;
const originalMoveTo = CanvasRenderingContext2D.prototype.moveTo;
const originalLineTo = CanvasRenderingContext2D.prototype.lineTo;
CanvasRenderingContext2D.prototype.moveTo = function(x, y) {
lastMoveTo = [x, y]; // Guardamos el punto anterior
return originalMoveTo.call(this, x, y);
};
CanvasRenderingContext2D.prototype.lineTo = function(x, y) {
if (!GRID_ENABLED && lastMoveTo) {
const [x0, y0] = lastMoveTo;
if (
x >= 0 && x <= maxWidth &&
y >= 0 && y <= maxHeight &&
isGridLinePair(x0, y0, x, y)
) {
return this;
}
}
return originalLineTo.call(this, x, y);
};
});
})();
(function () {
'use strict';
console.log("x-RedDragon Client Visuals Loaded");
const config = window.config || {};
config.skinColors = [
"#bf8f54", "#4c4c4c", "#896c4b",
"#fadadc", "#ececec", "#c37373",
"#000000", "#ecaff7", "#738cc3",
"#8bc373", "#91b2db"
];
window.config = config;
const css = `
body {
background: linear-gradient(135deg, rgba(15, 15, 15, 0.2), rgba(28, 28, 28, 0.2));
font-family: 'Orbitron', sans-serif;
color: white;
}
#mainMenu {
background-color: black !important;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.menuButton {
background-color: rgba(0, 0, 0, 0.6);
border: 2px solid #ff3333;
color: #ffffff;
font-family: 'Orbitron', sans-serif;
font-size: 18px;
font-weight: bold;
padding: 12px 28px;
border-radius: 14px;
cursor: pointer;
box-shadow: 0 0 15px #ff0000;
transition: all 0.4s ease-in-out;
text-shadow: 0 0 6px #ff0000;
}
.menuButton.epic-hover:hover {
background: linear-gradient(135deg, #ff1111, #ff0000, #cc0000);
background-size: 300% 300%;
animation: redPulse 3s infinite ease-in-out;
box-shadow: 0 0 25px #ff0000, 0 0 50px #ff1111;
color: #fff;
transform: scale(1.1);
border: 3px solid #ffffff;
}
@keyframes redPulse {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
#gameName {
font-size: 80px !important;
font-weight: bold;
color: #ff2222 !important;
text-shadow: 0 0 12px #ff0000, 0 0 30px #ff1111;
animation: shimmer 3s infinite;
font-family: 'Courier New', monospace !important;
position: relative;
top: -50px;
text-align: center;
transition: all 0.5s ease;
}
@keyframes shimmer {
0% { text-shadow: 0 0 12px #ff0000, 0 0 30px #ff1111; }
50% { text-shadow: 0 0 20px #ff3333, 0 0 40px #ff0000; }
100% { text-shadow: 0 0 12px #ff0000, 0 0 30px #ff1111; }
}
#gameName::after {
content: "𝕩-ℝ𝕖𝕕𝔻𝕣𝕒𝕘𝕠𝕟 ℂ𝕝𝕚𝕖𝕟𝕥";
display: block;
font-size: 1.1em;
color: #ff4444;
text-shadow: 0 0 25px #ff1111, 0 0 45px #ff0000;
animation: glowtext 2s infinite alternate;
position: relative;
top: 10px;
text-align: center;
}
@keyframes glowtext {
0% { opacity: 1; }
100% { opacity: 0.7; }
}
#loadingScreen {
background: rgba(0, 0, 0, 0.2) !important;
box-shadow: none !important;
border: none !important;
}
#loadingScreen::before {
content: "Cargando x-RedDragon Client...";
font-size: 28px;
color: #ff2222;
text-shadow: 0 0 12px #ff0000;
margin-bottom: 20px;
animation: pulseText 2s infinite;
}
@keyframes pulseText {
0% { opacity: 1; transform: scale(1); }
50% { opacity: 0.8; transform: scale(1.05); }
100% { opacity: 1; transform: scale(1); }
}
.menuCard, #bottomText, #storeHolder, #youtuberBtn, #adCard, .setNameContainer, .newsHolder {
background-color: rgba(20, 20, 20, 0.2);
padding: 20px;
border: 2px solid #ff3333;
box-shadow: 0 0 25px rgba(255, 0, 0, 0.8), 0 0 35px rgba(255, 50, 50, 0.6);
color: #fff !important;
animation: borderGlow 4s linear infinite;
text-align: center;
}
@keyframes borderGlow {
0% { box-shadow: 0 0 12px #ff0000; }
50% { box-shadow: 0 0 25px #ff3333, 0 0 35px #ff1111; }
100% { box-shadow: 0 0 12px #ff0000; }
}
input, select {
background-color: rgba(31, 31, 31, 0.2) !important;
color: #fff !important;
border: 1px solid #ff3300 !important;
border-radius: 10px;
padding: 10px;
text-align: center;
}
#gameUI .joinAlBtn, a {
animation: 5s infinite linear both normal redRainbow;
}
@keyframes redRainbow {
0% { filter: brightness(1) hue-rotate(0deg); }
100% { filter: brightness(1.2) hue-rotate(360deg); }
}
.resourceDisplay, #killCounter {
width: 120px;
margin: 0 auto;
border: 3px solid #ff2222;
border-radius: 12px;
background-color: rgba(50, 0, 0, 0.3);
color: #fff;
padding: 8px;
text-align: center;
font-size: 14px;
box-shadow: 0 0 10px #ff0000;
}
.uiElement {
border: 2px solid #ff4444;
background-color: rgba(40, 0, 0, 0.25);
padding: 10px;
color: #fff;
font-size: 14px;
text-align: center;
box-shadow: 0 0 10px #ff1111;
}
.actionBarItem {
border: 3px solid #ff4444;
border-radius: 50%;
width: 65px;
height: 65px;
background-position: center;
background-size: 55px 55px;
background-color: rgba(255, 0, 0, 0.2);
box-shadow: 0 0 15px #ff0000;
transition: transform 0.2s ease-in-out;
}
.actionBarItem:hover {
transform: scale(1.1);
box-shadow: 0 0 18px #ff6666;
background-color: rgba(255, 70, 70, 0.35);
}
#itemInfoHolder {
position: absolute;
top: 25px;
left: 50%;
transform: translateX(-50%);
width: 350px;
background-color: rgba(0, 0, 0, 0.3);
border: 2px solid #ff3333;
border-radius: 12px;
color: white;
padding: 10px;
font-size: 14px;
text-align: center;
box-shadow: 0 0 15px #ff0000;
}
::-webkit-scrollbar {
width: 12px;
}
::-webkit-scrollbar-track {
background: rgba(0,0,0,0.2);
margin-top: 10px;
margin-bottom: 10px;
}
::-webkit-scrollbar-thumb {
background: #ff3333;
border-radius: 0px;
box-shadow: 0 0 12px #ff3333;
}
`;
const style = document.createElement('style');
style.innerText = css;
document.head.appendChild(style);
const observer = new MutationObserver(() => {
const gameName = document.getElementById('gameName');
if (gameName && gameName.innerText !== '𝕩-ℝ𝕖𝕕𝔻𝕣𝕒𝕘𝕠𝕟 ℂ𝕝𝕚𝕖𝕟𝕥') {
gameName.innerText = '';
}
});
observer.observe(document.body, { childList: true, subtree: true });
console.log("x-RedDragon Client Full Customization Applied.");
const waitForMap = setInterval(() => {
const map = document.getElementById("mapDisplay");
if (map) {
map.style.backgroundImage = "url('http://i.imgur.com/Qllo1mA.png')";
map.style.backgroundSize = "cover";
map.style.border = "3px solid #ff3333";
map.style.boxShadow = "0 0 15px #ff0000";
map.style.borderRadius = "8px";
clearInterval(waitForMap);
}
}, 1);
})();
(function() {
'use strict';
// Cambiar título
document.title = "𝕩-ℝ𝕖𝕕𝔻𝕣𝕒𝕘𝕠𝕟 ℂ𝕝𝕚𝕖𝕟𝕥";
// Eliminar favicons antiguos
const oldIcons = document.querySelectorAll("link[rel*='icon']");
oldIcons.forEach(icon => icon.remove());
// Crear y añadir nuevo favicon
const newFavicon = document.createElement("link");
newFavicon.rel = "icon";
newFavicon.type = "image/png";
newFavicon.href = "https://i.imgur.com/vXgDJSp.png";
document.head.appendChild(newFavicon);
})();
(function () {
'use strict';
// Variables para FPS y Ping
let frameCount = 0;
let fps = 0;
let ping = 0;
let lastLoop = performance.now();
let pingTimes = [];
// Función para obtener el ping real con fetch y promediarlo
function updatePing() {
const startTime = performance.now();
fetch(window.location.href, { method: 'HEAD', cache: "no-store" })
.then(() => {
const endTime = performance.now();
const latency = Math.round(endTime - startTime);
pingTimes.push(latency);
if (pingTimes.length > 10) pingTimes.shift();
ping = Math.round(pingTimes.reduce((a, b) => a + b, 0) / pingTimes.length);
})
.catch(() => {
ping = 0;
});
setTimeout(updatePing, 1000); // Actualiza cada 1s
}
updatePing();
// FPS con requestAnimationFrame
function updateFPS() {
const now = performance.now();
frameCount++;
if (now - lastLoop >= 1000) {
fps = Math.round(frameCount / ((now - lastLoop) / 1000));
frameCount = 0;
lastLoop = now;
}
requestAnimationFrame(updateFPS);
}
updateFPS();
// Crear el contador visual con el nuevo diseño Orbitron
const statsContainer = document.createElement('div');
statsContainer.id = 'fpsPingDisplay';
Object.assign(statsContainer.style, {
position: 'fixed',
top: '12px',
left: '50%',
transform: 'translateX(-50%)',
fontSize: '22px',
fontFamily: "'Orbitron', sans-serif",
color: '#FFFFFF',
textShadow: '0 0 3px #000000, 1px 1px 0 #000, -1px -1px 0 #000',
background: 'none',
padding: '0',
borderRadius: '0',
border: 'none',
boxShadow: 'none',
zIndex: '0', // Debajo de los botones
pointerEvents: 'none'
});
document.body.appendChild(statsContainer);
// Actualiza cada 500ms el texto visible
function updateStatsDisplay() {
statsContainer.textContent = `FPS: ${fps} | Ping: ${ping}ms`;
setTimeout(updateStatsDisplay, 500);
}
updateStatsDisplay();
// Acceso desde consola opcional
window.getStats = function () {
return { fps, ping };
};
})();
(function () {
"use strict";
let ws;
const msgpack = window.msgpack;
WebSocket.prototype._send = WebSocket.prototype.send;
WebSocket.prototype.send = function (m) {
if (!ws) {
ws = this;
window.ws = this;
}
this._send(m);
};
let hue = 0;
function changeStyle() {
const ageBarBody = document.getElementById("ageBarBody");
if (ageBarBody) {
hue = (hue + 2) % 360;
const rgbColor = `hsl(${hue}, 100%, 50%)`;
ageBarBody.style.backgroundColor = rgbColor;
ageBarBody.style.border = `2px solid ${rgbColor}`;
ageBarBody.style.transform = "translateY(-2px)";
}
}
setInterval(changeStyle, 100);
const previousReloadStates = new Map();
const turretCooldown = 2500;
let turretLastClickTime = null;
let turretReady = true;
window.Cow.setCodec(msgpack);
CanvasRenderingContext2D.prototype._roundRect = CanvasRenderingContext2D.prototype.roundRect;
window.addEventListener("mousedown", (e) => {
if (e.button === 1 && turretReady) {
turretLastClickTime = Date.now();
turretReady = false;
}
});
window.Cow.addRender("global", () => {
const ctx = window.Cow.renderer.context;
window.Cow.playersManager.eachVisible(player => {
if (!player || !player.alive) return;
const width = window.config.healthBarWidth / 2 - window.config.healthBarPad / 2;
const yOffset = player.renderY + player.scale + window.config.nameY - 5;
const primaryReload = Math.min(Math.max(player.reloads.primary.count / player.reloads.primary.max, 0), 1);
const secondaryReload = Math.min(Math.max(player.reloads.secondary.count / player.reloads.secondary.max, 0), 1);
const pad = window.config.healthBarPad;
const height = 17;
const radius = 8;
// Barra primaria
ctx.save();
ctx.fillStyle = "#3d3f42";
ctx.translate(player.renderX - width * 1.19, yOffset);
ctx.beginPath();
ctx._roundRect(-width - pad, -8.5, 2 * width + 2 * pad, height, radius);
ctx.fill();
ctx.restore();
ctx.save();
ctx.fillStyle = player.isAlly ? "#8ecc51" : "#cc5151";
ctx.translate(player.renderX - width * 1.19, yOffset);
ctx.beginPath();
ctx._roundRect(-width, -8.5 + pad, 2 * width * primaryReload, height - 2 * pad, radius - 1);
ctx.fill();
ctx.restore();
// Barra secundaria
ctx.save();
ctx.fillStyle = "#3d3f42";
ctx.translate(player.renderX + width * 1.19, yOffset);
ctx.beginPath();
ctx._roundRect(-width - pad, -8.5, 2 * width + 2 * pad, height, radius);
ctx.fill();
ctx.restore();
ctx.save();
ctx.fillStyle = player.isAlly ? "#8ecc51" : "#cc5151";
ctx.translate(player.renderX + width * 1.19, yOffset);
ctx.beginPath();
ctx._roundRect(-width, -8.5 + pad, 2 * width * secondaryReload, height - 2 * pad, radius - 1);
ctx.fill();
ctx.restore();
// === Contador de HP ===
const hpCurrent = Math.floor(player.health);
const hpMax = Math.floor(player.maxHealth || 100);
const hpText = `HP: ${hpCurrent}/${hpMax}`;
ctx.save();
ctx.font = "bold 20px GameFont"; // Usa GameFont, o Arial si no carga
ctx.textAlign = "center";
ctx.lineWidth = 3;
ctx.strokeStyle = "black";
ctx.fillStyle = "white";
const textX = player.renderX;
const textY = yOffset + 32; // 12 píxeles más abajo
ctx.strokeText(hpText, textX, textY); // Borde negro
ctx.fillText(hpText, textX, textY); // Letra blanca
ctx.restore();
});
});
})();
// @require https://update.greasyfork.org/scripts/480301/1283571/CowJS.js
// @require https://update.greasyfork.org/scripts/480303/1282926/MooUI.js
(function () {
"use strict";
// Asegurarse de que el script se inicie después de que el juego esté completamente cargado
function init() {
if (!window.Cow || !window.CowUtils) {
setTimeout(init, 100);
return;
}
const { Cow, CowUtils } = window;
const settings = {
"health-bars": true,
"circle-bars": false,
"in-look-dir": false,
"friendly-color": "#56e319",
"enemy-color": "#ff3333",
"hit-counter": true
};
// Función para determinar si una construcción pertenece al jugador
function isOwnBuilding(object) {
// Verificar si tiene la propiedad de grupo (team)
if (object && Cow.player) {
// En MooMoo.io la propiedad group o team indica el equipo
if (object.group !== undefined && Cow.player.group !== undefined) {
return object.group === Cow.player.group;
}
// Alternativa: verificar por ownerID o owner.sid
if (object.owner && Cow.player.sid) {
return object.owner.sid === Cow.player.sid;
}
// Otra alternativa: comprobar teamID o team
if (object.team !== undefined && Cow.player.team !== undefined) {
return object.team === Cow.player.team;
}
}
// Por defecto si no podemos determinar, asumimos que es enemiga
return false;
}
function drawHealthBar(context, object) {
const healthBarWidth = 50; // Ancho predeterminado si window.config no está disponible
const healthBarPad = 5; // Padding predeterminado si window.config no está disponible
// Usar configuración del juego si está disponible
const width = (window.config && window.config.healthBarWidth ? window.config.healthBarWidth / 2 : healthBarWidth / 2) -
(window.config && window.config.healthBarPad ? window.config.healthBarPad / 2 : healthBarPad / 2);
const height = 17;
const radius = 8;
// Determinar el color basado en si la construcción es del jugador o enemiga
const barColor = isOwnBuilding(object) ? settings["friendly-color"] : settings["enemy-color"];
context.save();
context.translate(object.renderX || object.x, object.renderY || object.y);
// Fondo de la barra de vida
context.fillStyle = "#3d3f42";
if (context.roundRect) {
context.roundRect(-width - healthBarPad, -height / 2, 2 * width + 2 * healthBarPad, height, radius);
} else {
// Alternativa si roundRect no está disponible
context.beginPath();
context.moveTo(-width - healthBarPad + radius, -height / 2);
context.arcTo(-width - healthBarPad + 2 * width + 2 * healthBarPad, -height / 2, -width - healthBarPad + 2 * width + 2 * healthBarPad, -height / 2 + height, radius);
context.arcTo(-width - healthBarPad + 2 * width + 2 * healthBarPad, -height / 2 + height, -width - healthBarPad, -height / 2 + height, radius);
context.arcTo(-width - healthBarPad, -height / 2 + height, -width - healthBarPad, -height / 2, radius);
context.arcTo(-width - healthBarPad, -height / 2, -width - healthBarPad + radius, -height / 2, radius);
context.closePath();
}
context.fill();
// Barra de vida con el color según propiedad
context.fillStyle = barColor;
const healthRatio = (object.health || object.hp || 100) / (object.maxHealth || object.maxHP || 100);
if (context.roundRect) {
context.roundRect(-width, -height / 2 + healthBarPad, 2 * width * healthRatio, height - 2 * healthBarPad, radius - 1);
} else {
// Alternativa si roundRect no está disponible
context.beginPath();
context.moveTo(-width + (radius - 1), -height / 2 + healthBarPad);
context.arcTo(-width + 2 * width * healthRatio, -height / 2 + healthBarPad, -width + 2 * width * healthRatio, -height / 2 + height - 2 * healthBarPad, radius - 1);
context.arcTo(-width + 2 * width * healthRatio, -height / 2 + height - 2 * healthBarPad, -width, -height / 2 + height - 2 * healthBarPad, radius - 1);
context.arcTo(-width, -height / 2 + height - 2 * healthBarPad, -width, -height / 2 + healthBarPad, radius - 1);
context.arcTo(-width, -height / 2 + healthBarPad, -width + (radius - 1), -height / 2 + healthBarPad, radius - 1);
context.closePath();
}
context.fill();
// Mostrar texto de propietario para depuración (quitar en producción)
/*
context.fillStyle = "#fff";
context.font = "10px Arial";
context.fillText(isOwnBuilding(object) ? "Mío" : "Enemigo", 0, -30);
if (object.group !== undefined) context.fillText("Group: " + object.group, 0, -42);
if (object.team !== undefined) context.fillText("Team: " + object.team, 0, -54);
if (object.owner) context.fillText("Owner: " + (object.owner.sid || "?"), 0, -66);
*/
context.restore();
}
function drawHitCounter(context, object) {
if (!Cow.player || !Cow.player.weapon) return;
try {
const damage = Cow.player.weapon.dmg * (Cow.items.variants[Cow.player.weaponVariant]?.val || 1);
const damageAmount = damage * (Cow.player.weapon.sDmg || 1) * (Cow.player.skin?.id === 40 ? 3.3 : 1);
const objectHealth = object.health || object.hp || 100;
const hits = Math.ceil(objectHealth / damageAmount);
const offsetY = 22;
context.save();
context.font = `18px Hammersmith One`;
context.fillStyle = "#fff";
context.textBaseline = "middle";
context.textAlign = "center";
context.lineWidth = 8;
context.lineJoin = "round";
context.translate(object.renderX || object.x, object.renderY || object.y);
context.strokeText(hits, 0, offsetY);
context.fillText(hits, 0, offsetY);
context.restore();
} catch (e) {
console.error("Error en hit counter:", e);
}
}
// Función principal para renderizar las barras de vida
function renderHealthBars() {
if (!Cow.player) return;
const { context } = Cow.renderer;
try {
Cow.objectsManager.eachVisible((object) => {
// Solo procesar objetos que sean estructuras/items
if (!object.isItem && !object.isStructure && !object.buildingType && !object.type) return;
// Solo procesar objetos que tengan salud
if ((object.health === undefined && object.hp === undefined) ||
(object.maxHealth === undefined && object.maxHP === undefined)) return;
const angle = CowUtils.getDirection ?
CowUtils.getDirection(object, Cow.player) :
Math.atan2(object.y - Cow.player.y, object.x - Cow.player.x);
// Verificar si está en la dirección de mirada si esta opción está activada
if (settings["in-look-dir"] &&
CowUtils.getAngleDist &&
CowUtils.getAngleDist(angle, Cow.player.lookAngle) > (Cow.config?.gatherAngle || Math.PI/2)) {
return;
}
// Dibujar contador de golpes si está activado
if (settings["hit-counter"]) {
drawHitCounter(context, object);
}
// Dibujar barra de vida si está activado
if (settings["health-bars"] && !settings["circle-bars"]) {
drawHealthBar(context, object);
}
});
} catch (e) {
console.error("Error en renderHealthBars:", e);
}
}
// Agregar la función de renderizado al loop del juego
if (Cow.addRender) {
Cow.addRender("building-health-bars", renderHealthBars);
} else {
// Alternativa si addRender no está disponible: usar requestAnimationFrame
function renderLoop() {
try {
if (Cow && Cow.player && Cow.renderer && Cow.renderer.context) {
renderHealthBars();
}
} catch (e) {
console.error("Error en renderLoop:", e);
}
requestAnimationFrame(renderLoop);
}
requestAnimationFrame(renderLoop);
}
console.log("[Building Ownership Health Bars] Script iniciado correctamente");
}
// Iniciar el script
if (document.readyState === "complete") {
init();
} else {
window.addEventListener("load", init);
}
})();
(function () {
'use strict';
let ws = null;
let { msgpack } = window;
let playerID = null;
let myPlayer = {
id: null, x: null, y: null, dir: null, object: null, weapon: null,
clan: null, isLeader: null, maxXP: 300, XP: 0, age: 1,
hat: null, accessory: null, isSkull: null, maxHealth: 100,
health: 100
};
let players = [], enemy = [], nearestEnemy = {};
let gameCanvas = document.getElementById("gameCanvas");
let width = window.innerWidth;
let height = window.innerHeight;
let mouseX, mouseY;
const sendPacket = (packet, ...data) => {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(new Uint8Array(msgpack.encode([packet, data])));
}
};
const updatePlayers = data => {
players = [];
for (let i = 0; i < data[1].length / 13; i++) {
const playerInfo = data[1].slice(i * 13, i * 13 + 13);
if (playerInfo[0] === myPlayer.id) {
myPlayer.x = playerInfo[1];
myPlayer.y = playerInfo[2];
myPlayer.dir = playerInfo[3];
myPlayer.object = playerInfo[4];
myPlayer.weapon = playerInfo[5];
myPlayer.clan = playerInfo[7];
myPlayer.isLeader = playerInfo[8];
myPlayer.hat = playerInfo[9];
myPlayer.accessory = playerInfo[10];
myPlayer.isSkull = playerInfo[11];
} else {
players.push({
id: playerInfo[0],
x: playerInfo[1],
y: playerInfo[2],
dir: playerInfo[3],
object: playerInfo[4],
weapon: playerInfo[5],
clan: playerInfo[7],
isLeader: playerInfo[8],
hat: playerInfo[9],
accessory: playerInfo[10],
isSkull: playerInfo[11]
});
}
}
};
const updateHealth = (health, playerIDCheck) => {
if (myPlayer.id === playerIDCheck) {
myPlayer.health = health;
console.log("[🩸 Salud actualizada]", health);
}
};
const handleMessage = (message) => {
const decoded = msgpack.decode(new Uint8Array(message.data));
const data = Array.isArray(decoded) && decoded.length > 1 ? [decoded[0], ...decoded[1]] : decoded;
if (!data) return;
const type = data[0];
if (type === "C" && myPlayer.id == null) {
myPlayer.id = data[1];
console.log("[✔️ Verificación] ID del jugador:", myPlayer.id);
}
if (type === "a") updatePlayers(data);
if (type === "O") updateHealth(data[2], data[1]);
};
const socketFound = (sock) => {
sock.addEventListener("message", handleMessage);
if (gameCanvas) {
gameCanvas.addEventListener("mousemove", ({ x, y }) => {
mouseX = x;
mouseY = y;
});
}
window.addEventListener("resize", () => {
width = window.innerWidth;
height = window.innerHeight;
});
};
WebSocket.prototype.oldSend = WebSocket.prototype.send;
WebSocket.prototype.send = function (m) {
if (!ws) {
ws = this;
document.websocket = this;
socketFound(this);
console.log("[🔌 WebSocket] Interceptado correctamente.");
}
this.oldSend(m);
};
// Anti-Altcha (captcha visual)
const altchaCheck = setInterval(() => {
let altcha = document.getElementById('altcha');
let altchaBox = document.getElementById('altcha_checkbox');
if (altcha && altchaBox) {
altcha.style.display = 'none';
altchaBox.checked = true;
altchaBox.click();
clearInterval(altchaCheck);
console.log("[🚫 Altcha] Eliminado.");
}
}, 500);
})();
// AutoReload
(function() {
"use strict";
// Función para retrasar la ejecución (sleep)
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Recarga la página después de 1.5 segundos
async function refreshPage() {
await delay(1500);
window.onbeforeunload = null;
location.reload();
}
// Crea un hook (interceptor) para la propiedad indicada en el objeto target
function interceptProperty(target, propName, onSetCallback) {
const hiddenKey = Symbol(propName);
Object.defineProperty(target, propName, {
get() {
return this[hiddenKey];
},
set(value) {
onSetCallback(this, hiddenKey, value);
},
configurable: true
});
}
// Intercepta una función para envolverla y modificar su comportamiento
function wrapFunction(originalFunc, wrapper) {
return new Proxy(originalFunc, {
apply(target, thisArg, args) {
return wrapper.call(thisArg, target, args);
}
});
}
// Hook para capturar cualquier asignación a "errorCallback" en Object.prototype
interceptProperty(Object.prototype, "errorCallback", (obj, key, val) => {
obj[key] = val;
if (typeof val !== "function") return;
// Reemplaza la función original con un Proxy que recarga la página
obj[key] = wrapFunction(val, (target, args) => {
// Anula alert para evitar alertas molestas
window.alert = () => {};
// Recarga la página
refreshPage();
// Ejecuta la función original
return target.apply(this, args);
});
});
// Intercepta setters de eventos de WebSocket (onclose y onerror)
["onclose", "onerror"].forEach(eventName => {
const descriptor = Object.getOwnPropertyDescriptor(WebSocket.prototype, eventName);
if (!descriptor || !descriptor.set) return;
Object.defineProperty(WebSocket.prototype, eventName, {
set(handler) {
// Envuelve el handler original para recargar la página al dispararse
const wrappedHandler = wrapFunction(handler, (target, args) => {
refreshPage();
return target.apply(this, args);
});
descriptor.set.call(this, wrappedHandler);
}
});
});
})();
(function () {
'use strict';
// ==== ESTILOS ====
const style = document.createElement('style');
style.textContent = `
#moddedMenu {
position: fixed;
top: 10px;
left: 10px;
width: 300px;
background: rgba(0, 0, 0, 0.95);
border: 4px solid #ff0000;
border-radius: 15px;
z-index: 9999;
color: white;
font-family: monospace;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
box-sizing: border-box;
opacity: 0;
transform: scale(0.5);
transition: opacity 0.3s ease, transform 0.3s ease;
pointer-events: none;
}
#moddedMenu.open {
opacity: 1;
transform: scale(1);
pointer-events: auto;
}
#moddedMenuContent {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
#menuTitle {
font-size: 1.6em;
color: #ff0000;
text-align: center;
margin-bottom: 20px;
}
.rd-select, .rd-button {
font-family: monospace;
width: 100%;
max-width: 90%;
font-size: 1em;
padding: 8px;
margin-bottom: 10px;
border: 1px solid #ff0000;
border-radius: 5px;
background: #111;
color: #ff0000;
outline: none;
}
.rd-button {
cursor: pointer;
font-weight: bold;
transition: background-color 0.3s ease;
}
.rd-button:hover {
background-color: #cc0000;
}
#buttons {
display: flex;
justify-content: space-between;
width: 90%;
gap: 10px;
}
.toggle-switch {
display: flex;
align-items: center;
gap: 10px;
margin-top: 10px;
margin-bottom: 10px;
color: #ff0000;
}
.toggle-switch input[type="checkbox"] {
width: 40px;
height: 20px;
appearance: none;
background: #222;
border: 2px solid #ff0000;
border-radius: 20px;
position: relative;
cursor: pointer;
outline: none;
transition: background 0.3s ease;
}
.toggle-switch input[type="checkbox"]::before {
content: '';
position: absolute;
width: 16px;
height: 16px;
border-radius: 50%;
background: #ff0000;
top: 1px;
left: 1px;
transition: transform 0.3s ease;
}
.toggle-switch input[type="checkbox"]:checked::before {
transform: translateX(20px);
}
`;
document.head.appendChild(style);
// ==== ELEMENTOS DEL MENÚ ====
const musicTracks = [
{ name: "Alan Walker - Faded", url: "https://files.catbox.moe/p9om0y.mp3" },
{ name: "Alan Walker - Alone", url: "https://files.catbox.moe/xptxat.mp3" },
{ name: "TheFatRat - Unity", url: "https://files.catbox.moe/cx5uyo.mp3" },
{ name: "TheFatRat - Monody", url: "https://files.catbox.moe/7fwpbl.mp3" },
{ name: "NEFFEX - Fight Back", url: "https://files.catbox.moe/iif5rx.mp3" },
{ name: "NEFFEX - Never Give Up", url: "https://files.catbox.moe/2pamid.mp3" },
{ name: "Alan Walker - Dust", url: "https://files.catbox.moe/w1sb9x.mp3" },
{ name: "Alan Walker - Catch Me", url: "https://files.catbox.moe/74b06c.mp3" },
{ name: "Alan Walker - Last Song", url: "https://files.catbox.moe/nuta1v.mp3" },
{ name: "Egzod - Royalty ft. Neoni", url: "https://files.catbox.moe/zlfc04.mp3" }
];
const menu = document.createElement('div');
menu.id = 'moddedMenu';
const content = document.createElement('div');
content.id = 'moddedMenuContent';
const title = document.createElement('div');
title.id = 'menuTitle';
title.textContent = '𝕄𝕦𝕤𝕚𝕔𝕤';
const musicSelect = document.createElement('select');
musicSelect.className = 'rd-select';
musicTracks.forEach(track => {
const option = document.createElement('option');
option.value = track.url;
option.textContent = track.name;
musicSelect.appendChild(option);
});
const buttonsDiv = document.createElement('div');
buttonsDiv.id = 'buttons';
const playBtn = document.createElement('button');
playBtn.className = 'rd-button';
playBtn.textContent = '▶ Play';
const stopBtn = document.createElement('button');
stopBtn.className = 'rd-button';
stopBtn.textContent = '■ Stop';
buttonsDiv.appendChild(playBtn);
buttonsDiv.appendChild(stopBtn);
const modeSelect = document.createElement('select');
modeSelect.className = 'rd-select';
modeSelect.innerHTML = `
<option value="repeat">🔁 Repeat Current</option>
<option value="next">⏭️ Play Next</option>
`;
const audio = document.createElement('audio');
audio.id = 'audioPlayer';
const visualsTitle = document.createElement('div');
visualsTitle.textContent = '𝔼𝕩𝕥𝕣𝕒';
visualsTitle.style.color = '#ff0000';
visualsTitle.style.fontSize = '1.4em';
visualsTitle.style.marginTop = '25px';
visualsTitle.style.marginBottom = '10px';
visualsTitle.style.fontWeight = 'bold';
visualsTitle.style.textAlign = 'center';
const toggleContainer = document.createElement('div');
toggleContainer.className = 'toggle-switch';
const toggleLabel = document.createElement('span');
toggleLabel.textContent = 'Tracers';
const toggleInput = document.createElement('input');
toggleInput.type = 'checkbox';
toggleContainer.appendChild(toggleLabel);
toggleContainer.appendChild(toggleInput);
content.appendChild(title);
content.appendChild(musicSelect);
content.appendChild(buttonsDiv);
content.appendChild(modeSelect);
content.appendChild(visualsTitle);
content.appendChild(toggleContainer);
content.appendChild(audio);
menu.appendChild(content);
document.body.appendChild(menu);
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
menu.classList.toggle('open');
}
});
playBtn.addEventListener('click', () => {
if (audio.src !== musicSelect.value) {
audio.src = musicSelect.value;
}
audio.play();
});
stopBtn.addEventListener('click', () => {
audio.pause();
audio.currentTime = 0;
});
musicSelect.addEventListener('change', () => {
if (!audio.paused) {
audio.src = musicSelect.value;
audio.play();
}
});
audio.addEventListener('ended', () => {
if (modeSelect.value === 'repeat') {
audio.currentTime = 0;
audio.play();
} else if (modeSelect.value === 'next') {
const currentIndex = musicSelect.selectedIndex;
const nextIndex = (currentIndex + 1) % musicSelect.options.length;
musicSelect.selectedIndex = nextIndex;
audio.src = musicSelect.value;
audio.play();
}
});
// ==== TRACERS ====
localStorage.setItem("moofoll", true);
const players = [];
const property = "team";
Object.defineProperty(Object.prototype, property, {
get: function () {
if (this.isPlayer && !players.find(p => p.id === this.id)) {
players.push(this);
}
return this[`_${property}`];
},
set: function (value) {
this[`_${property}`] = value;
},
configurable: true
});
let ctx, owner, camX = 0, camY = 0;
const conf = {
radar: {
colorEnemy: "#ffffff",
w: 20,
h: 20
},
maxScreenWidth: 1920,
maxScreenHeight: 1080
};
const createArrow = (id) => {
const el = document.createElement("div");
el.id = `radarArrow-${id}`;
el.style.position = "absolute";
el.style.width = "0";
el.style.height = "0";
el.style.borderStyle = "solid";
el.style.borderWidth = `10px 0 10px ${conf.radar.w}px`;
el.style.borderColor = `transparent transparent transparent ${conf.radar.colorEnemy}`;
el.style.display = "none";
el.style.zIndex = 999;
document.body.appendChild(el);
};
const updateArrow = (id, x, y, color = conf.radar.colorEnemy, opacity = 1, angle = 0) => {
const arrow = document.getElementById(`radarArrow-${id}`);
if (!arrow) return;
arrow.style.left = `${x}px`;
arrow.style.top = `${y}px`;
arrow.style.display = "block";
arrow.style.opacity = opacity;
arrow.style.transform = `rotate(${angle}deg)`;
arrow.style.borderColor = `transparent transparent transparent ${color}`;
};
const removeArrow = (id) => {
const arrow = document.getElementById(`radarArrow-${id}`);
if (arrow) arrow.remove();
};
const getDistance = (x1, y1, x2, y2) => Math.hypot(x2 - x1, y2 - y1);
const getDirection = (x1, y1, x2, y2) => Math.atan2(y2 - y1, x2 - x1);
const RtoD = (r) => r * 180 / Math.PI;
const radar = (delta) => {
if (!toggleInput.checked) {
players.forEach(p => removeArrow(p.id));
return;
}
if (!ctx) {
const gameCanvas = document.getElementById("gameCanvas");
if (!gameCanvas) return;
ctx = gameCanvas.getContext("2d");
}
owner = players.find(p => p.visible && p.wood);
if (!owner) return;
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2;
camX += ((owner.x - camX) * 0.1);
camY += ((owner.y - camY) * 0.1);
for (let player of players) {
if (player.id === owner.id || !player.visible || typeof player.x !== "number" || typeof player.y !== "number" || (player.team && player.team === owner.team)) {
removeArrow(player.id);
continue;
}
const arrowId = player.id;
let rad = getDirection(owner.x, owner.y, player.x, player.y);
let per = getDistance(0, 0, (owner.x - player.x), (owner.y - player.y) * (16 / 9)) * 100 / (conf.maxScreenHeight / 2);
let alpha = Math.min(per / centerY, 1);
let dis = centerY * alpha;
let tx = centerX + dis * Math.cos(rad) - conf.radar.w / 2;
let ty = centerY + dis * Math.sin(rad) - conf.radar.h / 2;
if (!document.getElementById(`radarArrow-${arrowId}`)) {
createArrow(arrowId);
}
updateArrow(arrowId, tx, ty, conf.radar.colorEnemy, alpha, RtoD(rad));
}
};
let lastTime = Date.now();
function gameLoop() {
requestAnimationFrame(gameLoop);
let now = Date.now();
radar(now - lastTime);
lastTime = now;
}
gameLoop();
})();