您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Arras classes for scripting
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/550169/1664159/Classes.js
// ==UserScript== // @name Classes // @namespace http://tampermonkey.net/ // @version 2025-09-20 // @description Arras classes for scripting // @author You // @match http://*/* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant none // ==/UserScript== (function() { 'use strict'; class BroadcastInfo { constructor() { this.clear(); } clear() { this.global_minimap = {}; this.team_minimap = {}; this.leaderboard = new Map(); } // get the id of a leaderboard item by rank get_lb_rank_id(rank) { let i = 0; for (const [id, lb_obj] of this.leaderboard.entries()) { if (i === rank) { return id; } i++; } return null; } serialize_team_minimap() { let bytes = (this.team_minimap.length * 7) + 2; let buf = new ArrayBuffer(bytes); let view = new DataView(buf); view.setUint16(0, this.team_minimap.length); let offset = 2; for (let id in this.team_minimap) { let obj = this.team_minimap[id]; view.setUint32(offset, id); view.setUint8(offset + 4, obj.x); view.setUint8(offset + 5, obj.y); view.setUint8(offset + 6, obj.color); offset += 7; } return buf; } deserialize_team_minimap(buf) { let view = new DataView(buf); let len = view.getUint16(0); let offset = 2; for (let i = 0; i < len; i++) { let id = view.getUint32(offset); let x = view.getUint8(offset + 4); let y = view.getUint8(offset + 5); let color = view.getUint8(offset + 6); this.team_minimap[id] = { x, y, color }; offset += 7; } } draw_global_minimap(ctx, rx, ry, norm_to_canvas) { // do global first ctx.globalAlpha = 0.5; for (const [id, global_mmap_obj] of Object.entries(this.global_minimap)) { const { x, y, color, type, size } = global_mmap_obj; const { cx, cy } = norm_to_canvas(x, y); ctx.fillStyle = COLOR_ID_MAP[color] || 'black'; switch (type) { case 0: // boss, rock case 1: { ctx.beginPath(); ctx.arc(cx, cy, size * c_factor, 0, 2 * Math.PI); ctx.fill(); ctx.closePath(); break; } // wall case 2: { let c_size = 2 * size * c_factor + 0.5; ctx.fillRect_c(cx, cy, c_size, c_size); break; } } } ctx.globalAlpha = 1.0; } draw(ctx, room_info) { // console.log(`team minimap length: ${Object.keys(this.team_minimap).length}`); const { x_min, y_min, x_max, y_max } = room_info; // minimap ctx.strokeStyle = 'black'; ctx.lineWidth = 2; // -x -y to +x +y (room) // divide by 10 let rx = x_max - x_min; let ry = y_max - y_min; let c_rx = rx * c_factor; let c_ry = ry * c_factor; // define a function to convert norm x,y to canvas x,y const norm_to_canvas = (nx, ny) => { return { cx: (nx - 0.5) * c_rx, cy: (ny - 0.5) * c_ry, }; }; this.draw_global_minimap(ctx, rx, ry, norm_to_canvas); this.draw_team_minimap(ctx, norm_to_canvas); } draw_team_minimap(ctx, norm_to_canvas) { ctx.font = 'bold 10px Ubuntu'; let queued_draws = []; for (const [id, team_mmap_obj] of Object.entries(this.team_minimap)) { let { x, y, color } = team_mmap_obj; x /= 255; y /= 255; // get the lb obj const lb_obj = this.leaderboard.get(Number(id)); let text = ''; if (lb_obj) { const { name, score, mockup_index } = lb_obj; let mockup_name = window.mockups.get_mockup_name(mockup_index); text = `${name} | ${mockup_name} | ${make_score_str(score)}`; } const { cx, cy } = norm_to_canvas(x, y); let is_target = target_id === Number(id); if (is_target) { window.target_player.update_target(x, y, color); } let score = lb_obj?.score; if (id == window.game_update.player.entity_id) { color = 19; // black score = 1e5; window.target_player.update_self(x, y); } let q = draw_minimap_dot(ctx, cx, cy, color, score, is_target); if (q) { queued_draws.push(q); } if (text) { // ctx.fillText(text, cx, cy); } } ctx.strokeStyle = 'black'; ctx.lineWidth = 2; for (const q of queued_draws) { let {x, y, radius, alpha} = q; radius *= 1.5; ctx.globalAlpha = alpha; let base_angle = curr_t / 5; for (let i = 0; i < 4; i++) { let angle = base_angle + i * Math.PI / 2; let dx = Math.cos(angle) * radius; let dy = Math.sin(angle) * radius; ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + dx, y + dy); ctx.stroke(); ctx.closePath(); } ctx.globalAlpha = 1; } } parse_broadcast_packet(packet, room_info) { let r = new Rotator(packet); // console.log(packet); this.parse_global_minimap_dels(r); this.parse_global_minimap(r); this.parse_team_minimap_dels(r); this.parse_team_minimap(r); this.parse_leaderboard_dels(r); this.parse_leaderboard(r); } parse_global_minimap_dels(r) { let len = r.next; for (let iter = 0; iter < len; iter++) { delete this.global_minimap[r.next]; } } parse_global_minimap(r) { let len = r.next; for (let iter = 0; iter < len; iter++) { this.global_minimap[r.next] = { type: r.next, x: r.next_n255, y: r.next_n255, color: r.next, size: r.next, }; } } parse_team_minimap_dels(r) { let len = r.next; for (let iter = 0; iter < len; iter++) { delete this.team_minimap[r.next]; } } parse_team_minimap(r) { let len = r.next; for (let iter = 0; iter < len; iter++) { let id = r.next; this.team_minimap[id] = { x: r.next, y: r.next, color: r.next, }; } } parse_leaderboard_dels(r) { let len = r.next; for (let iter = 0; iter < len; iter++) { this.leaderboard.delete(r.next); } } parse_leaderboard(r) { let len = r.next; for (let iter = 0; iter < len; iter++) { this.leaderboard.set(r.next, { score: r.next, mockup_index: r.next, name: r.str_sm5, color: r.next, bar_color: r.next, }); } // sort by score, descending this.leaderboard = new Map([...this.leaderboard.entries()].sort((a, b) => b[1].score - a[1].score)); } } class RoomInfo { constructor() { this.x_min = 0; this.y_min = 0; this.x_max = 0; this.y_max = 0; this.x_size = 0; this.y_size = 0; this.wall_size = 1e9; this.walls_init = false; this.layout = []; } // normalized [0,1] (may be slightly outside bounds) to grid coord (0 to xsize, 0 to ysize) normalized_coord_to_grid_coord(nx, ny) { return { gx: Math.floor(nx * (this.x_size - 2)) + 1, gy: Math.floor(ny * (this.y_size - 2)) + 1, }; } // invert grid_coord_to_normalized_coord(gx, gy) { return { nx: (gx-1) / (this.x_size - 2), ny: (gy-1) / (this.y_size - 2), } } grid_coord_to_global_coord(gx, gy) { let { nx, ny } = this.grid_coord_to_normalized_coord(gx, gy); return { gx: (nx - 0.5) * this.x_size, gy: (ny - 0.5) * this.y_size, }; } draw(ctx) { const { x_min, y_min, x_max, y_max } = this; let rx = x_max - x_min; let ry = y_max - y_min; let wx = rx / (this.x_size); let wy = ry / (this.y_size); let c_rx = rx * c_factor; let c_ry = ry * c_factor; // minimap ctx.strokeStyle = 'black'; ctx.lineWidth = 2; ctx.globalAlpha = bg_opacity; ctx.fillStyle = BG_COLOR; ctx.fillRect_c(0, 0, c_rx, c_ry); ctx.strokeRect_c(0, 0, c_rx, c_ry); ctx.globalAlpha = 1; } parse_room_packet(packet) { // x min, y min, x max, y max, ?, ?, size_x, size_y, ... this.x_min = packet[0]; this.y_min = packet[1]; this.x_max = packet[2]; this.y_max = packet[3]; console.log(`room bounds: ${this.x_min}, ${this.y_min}, ${this.x_max}, ${this.y_max}`); console.log(`first 10 packet: ${packet.slice(0, 10)}`); let sx = packet[6]; let sy = packet[7]; // at their smallest, walls are half the size - so we need to double the size // we also include outer edge tiles (i.e. 2 extra, 1 on each side), since players can go there as well this.x_size = sx * 2 + 2; this.y_size = sy * 2 + 2; console.log(`true layout size: ${sx}, ${sy}`); let layout = []; // allocate the layout sx by sy (2d array) for (let x = 0; x < this.x_size; x++) { layout.push([]); for (let y = 0; y < this.y_size; y++) { layout[x].push(1); } } let offset = 8; for (let x = 0; x < sx; x++) { for (let y = 0; y < sy; y++) { let p_index = offset + x * sy + y; let val = packet[p_index]; if (!val) val = 18; for (let i = 0; i < 2; i++) { for (let j = 0; j < 2; j++) { let x_ = 1 + 2*x+i; let y_ = 1 + 2*y+j; layout[y_][x_] = val == 18? 1 : val; } } } } this.layout = layout; } } class Mockups { constructor() { this.mockups = new Map(); } get_mockup_name(id) { return this.mockups.get(id) || 'Unknown Entity'; } parse_mockup_packet(packet) { let strs = extract_strings(packet); for (let s of strs) { const { start, end, string } = s; if (string.length > 2) { // loc.start - 2 is the mockup id let mockup_id = packet[start - 2]; if (!this.mockups.has(mockup_id)) { this.mockups.set(mockup_id, string); } } } // sort the mockups by id let sorted_mockups = Array.from(this.mockups.entries()).sort((a, b) => a[0] - b[0]); this.mockups = new Map(sorted_mockups); } } class Entity { constructor() { this.x = undefined; this.y = undefined; this.angle = undefined; this.mockup_id = 0; // rendering flags probably (i.e. dmg immunity flash, etc) this.flags = 0; this.health = 255; this.shield = 255; this.opacity = 255; this.size = 0; this.score = 0; this.name = ""; this.color_id = 0; this.type = 0; } } class Player { constructor() { this.x = 0; this.y = 0; this.fov = 0; this.mockup_id = 0; this.color_id = undefined; this.entity_id = 0; this.score = 0; } } class GameUpdate { constructor() { this.player = new Player(); this.entities = new Map(); } parse_update_packet(packet) { let r = new Rotator(packet); let res = this.parse_player(r); if (!res) return; let next; // read until -1 while ((next = r.next) != -1) { // do nothing } this.parse_deleted_entities(r); this.parse_updated_entities(r); } parse_player(r) { this.player.x = r.next; this.player.y = r.next; this.player.fov = r.next; if (r.eop) return false; let flags = r.next; if (flags === 4094) { r.skip(1); this.player.mockup_id = r.next; r.skip(1); this.player.color_id = r.next; this.player.entity_id = r.next; this.player.score = r.next; } return true; } parse_deleted_entities(r) { let deleted_entities = []; let next; while ((next = r.next) != -1) { deleted_entities.push(next); } for (let id of deleted_entities) { this.entities.delete(id); } } parse_updated_entities(r, self_only = false) { while (!r.eop) { let id = r.next; let entity = this.entities.get(id) ?? new Entity(); let is_self = id === this.player.entity_id; if (self_only && !is_self) { continue; } entity = this.parse_entity(entity, r); if (is_self) { // copy all properties to player for (let key in entity) { this.player[key] = entity[key]; } our_pos = {x: this.player.x, y: this.player.y}; } this.entities.set(id, entity); } } parse_entity(entity, r) { let flags = r.next; let next; if (flags & (1<<0)) { let dx = r.next / 4; let dy = r.next / 4; if (entity.x === undefined || entity.y === undefined) { entity.x = dx; entity.y = dy; } else { entity.x += dx; entity.y += dy; } } if (flags & (1<<1)) { let v = r.next * Math.PI / 512; entity.angle = (entity.angle === undefined) ? v : entity.angle + v; } if (flags & (1<<2)) { entity.mockup_id = r.next; } if (flags & (1<<3)) { let l = r.index; while ((next = r.next) !== -1) { let gun_index = next; let gun_flags = r.next; let time, power; if (gun_flags & (1<<0)) { time = r.next; } if (gun_flags & (1<<1)) { power = r.next; } } } if (flags & (1<<4)) { while ((next = r.next) !== -1) { let turret_index = next; let _ = this.parse_entity(new Entity(), r, false); } } if (flags & (1<<5)) { entity.flags = r.next; } if (flags & (1<<6)) { entity.health = r.next; } if (flags & (1<<7)) { entity.shield = r.next; } if (flags & (1<<8)) { entity.opacity = r.next; } if (flags & (1<<9)) { entity.size = r.abs_next * 0.0625; } if (flags & (1<<10)) { entity.score = r.next; } if (flags & (1<<11)) { entity.name = r.str_sm5; } if (flags & (1<<12)) { entity.color_id = r.next; } if (flags & (1<<13)) { entity.type = r.next; } return entity; } } class TargetPlayer { constructor() { this.x = null; this.y = null; this.color = null; this.px = null; this.py = null; this.last_x = null; this.last_y = null; this.last_px = null; this.last_py = null; this.has_target = false; this.result = null; this.last_tile = {}; } get updated_positions() { return this.x !== this.last_x || this.y !== this.last_y || this.px !== this.last_px || this.py !== this.last_py; } get updated_self_positions() { return this.px !== this.last_px || this.py !== this.last_py; } get updated_target_positions() { return this.x !== this.last_x || this.y !== this.last_y; } update_lasts() { this.last_x = this.x; this.last_y = this.y; this.last_px = this.px; this.last_py = this.py; } update_self(nx, ny) { let res = window.room_info.normalized_coord_to_grid_coord(nx, ny); this.px = clamp(res.gx, 0, window.room_info.x_size - 1); this.py = clamp(res.gy, 0, window.room_info.y_size - 1); } peace() { this.has_target = false; } } class Rotator { constructor(packet, index = 0) { this.packet = packet; this._index = index; } get index() { return this._index; } set index(index) { this._index = index; } get next() { return this.packet[this._index++]; } // next / 255 get next_n255() { return this.next / 255; } get abs_next() { return Math.abs(this.next); } get undo() { return this.packet[--this._index]; } // skip n vals skip(n) { this._index += n; } // w/ len. str(len) { if (len > 1000) { console.log(`WARNING: string length is ${len}, skipping`); this.skip(len); return ''; } let remaining = len; let s = ''; while (remaining > 0) { let a = this.next; let b = this.next; if (-64 <= b && b < 0 && !(16 < a && a < 160)) { s += "💀"; // still dont have the full algo so im just gotta put a skull here as long as the length is right if (a > 3000) { // to be exact the smallest a for 4 byte is 36992, but lets just put 3000 here remaining -= 4; } else if (-16 <= a && a < 16) { remaining -= 2; } else { remaining -= 3; } // u+7ff or less, -2 bytes } else { s += String.fromCharCode(a); this.undo; remaining--; } } return s; } // 5 bit sign-magnitude get str_sm5() { let len = this.next; if (len < 0) { len = 32 + len; } return this.str(len); } get eop() { return this._index >= this.packet.length; } } })();