Greasy Fork

Greasy Fork is available in English.

Classes

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;
  }
}
})();