Classes

Arras classes for scripting

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greasyfork.org/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;
  }
}
})();