PrismarineJS / PrismarineJS/node-minecraft-protocol

Deserialization Error as Client

Open
#737 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

waiting for info
Dominant language
JavaScript
Stars
1.4k
Forks
290
Avg merge
4d 8h
Merged PRs (30d)
7

Description

[ ] The FAQ doesn't contain a resolution to my issue

Versions

  • minecraft-protocol: 1.13.0
  • server: vanilla/spigot/paper 1.16.1
  • node: 12.16.0

Detailed description of a problem

Got a type error while other player flaying over a jungle biome

Current code

import { Client, createClient } from 'minecraft-protocol';
import { IPacketMessage } from '../models/IPacketMessage';
import { IPacket } from '../models/IPacket';
import { ETranslate } from '../models/ETranslate';
import { MC_CLIENT } from '../config/config'
import * as axios from '../services/axios';
import { ITp } from '../models/ITp';
import { IUser } from '../models/IUser';
import { ICoordinates } from '../models/ICoordinates';


let tpDB: Array<ITp>;
let userDB: Array<IUser>;
let ops: Array<string>;
let timer: NodeJS.Timeout;

const dimensions: Array<string> = [
  'overworld',
  'the_nether',
  'the_end'
];

/**
 * Checks if the dimension exists.
 * @param dimension the name of the dimension.
 */
const checkDimension = (dimension: string): number => {
  switch (dimension) {
    case 'OVERWORLD':
      return 0;
    case 'NETHER':
      return 1;
    case 'END':
      return 2;
    default:
      return -1;
  }
}

/**
 * Load all databases necesary for the commands.
 */
const loadDBs = async (): Promise<void> => {
  tpDB = await axios.getTps();
  userDB = await axios.getUsers();
  ops = await axios.getOps();
}

/**
 * Checks if a user is an OP.
 * @param username The name of the user to check.
 */
const isOp = (username: string): boolean => {
  return username in ops;
}

/**
 * Add an user to the OP list.
 * @param op The name of the user adding new OP.
 * @param newop The name of the new OP user.
 * @returns `-1` if the user adding is not OP, `0` if the added user is already an OP, `1` if new user added.
 */
const addOp = (op: string, newop: string): number => {
  if (!isOp(op)) {
    return -1;
  }

  if (isOp(newop)) {
    return 0;
  }
  ops.push(newop);
  return 1;
}

/**
 * Adds a set amount of points to a scoreboard every certain time.
 * @param sbname Name of the scoreboard.
 * @param points Amount of points to add.
 * @param time The time between each adition.
 */
const startTimer = (sbname: string, points: number = 500, time: number = 3600000): void => {
  timer = setInterval(() => {
    write(`/scoreboard players add @a[name=!"${MC_CLIENT.username}"] ${sbname} ${points}`);
    write(`/tellraw @a [{"text":"[${MC_CLIENT.username}] ", "color":"gold"}, {"text":"Free Money Hour", "color":"gray"}]`);
  }, time);
}

const client: Client = createClient(MC_CLIENT);

/**
 * Basic chat message writer.
 * @param msg The message or messages to send to chat.
 */
const write = (msg: Array<string> | string): void => {
  if (msg instanceof Array) {
    for (const m of msg) {
      client.write('chat', { message: m });
    }
  } else {
    client.write('chat', { message: msg });
  }
}

client.on('success', (user): void => {
  // console.log(user);
  console.log('connected!');
  startTimer('Money');
  // read db here
  // loadDBs();
});
client.on('chat', (packet: IPacket): void => {
  // Listen for chat messages and echo them back.
  // console.log(packet)
  const jsonMsg: IPacketMessage = JSON.parse(packet.message);
  const msgWith: Array<any> = jsonMsg.with;
  if (!msgWith) return;
  console.log(JSON.stringify(jsonMsg, null, 2));
  // console.log(jsonMsg)
  // console.log(msgWith);
  const username: string = msgWith[0].text || msgWith[0].insertion;
  const tl: string = jsonMsg.translate;
  // console.log(tl === ETranslate.TEXT)
  let msg = '';
  if (!username) return;
  if (username === client.username || username === 'Server') return;
  // console.log(username);
  switch (true) {
    case (tl === ETranslate.TEXT):
      msg = msgWith[1];
      break;
    case (tl === ETranslate.INCOMING):
      msg = msgWith[1].text;
      break;
    default: return;
  }
  // console.log('m:', msg, 'u:', username);
  if (msg.length < 1) return;
  if (msg[0] !== '!') return;
  let args: Array<string> = msg.slice(1).split(' ');
  let cmd: string = args[0];
  args = args.slice(1);
  console.log(cmd, args);
  let response;
  switch (cmd) {
    case 'init':
      /* check for op */

      break;
    case 'register':
      // db.push()
      const rind = userDB.findIndex((u: IUser) => u.username === username);
      if (rind > -1) {
        write(`/tell ${username} You are already registered`);
        return;
      }
      write([
        `/tell ${username} Registered!`,
        `/tell ${username} Here is some money to start`,
        `/scoreboard players add ${username} Money 1000`
      ]);
      break;
    case 'tp':
      response = '';
      const name = args[0].toUpperCase();
      const uind = userDB.findIndex((u: IUser) => u.username === username);
      if (uind < 0) {
        write(`/tell ${username} Use '!register' to use this command`);
        return;
      }
      const d = args[1].toUpperCase();
      const id = checkDimension(d);
      if (id < 0) {
        write(`/tell ${username} Dimension '${d}' does not exists`)
        return;
      }
      const dn = dimensions[id];
      const tpind = userDB[uind].tp.findIndex((tp: ITp) =>
        tp.name === name && tp.dimension === dn);
      switch (args.length) {
        case 1:
          // do tp if exists//
          if (tpind < 0) {
            response = `/tell ${username} tp '${name}' in dimension '${d}' not registered`;
            break;
          }
          const tp = userDB[uind].tp[tpind].coordinates;
          // response = `/tp ${username} ${tp.x} ${tp.y} ${tp.z}`;
          response = `/execute in ${dn} run tp ${username} ${tp.x} ${tp.y} ${tp.z}`;
          break;
        case 5:
          // store tp //
          const cx = parseInt(args[1]);
          const cy = parseInt(args[2]);
          const cz = parseInt(args[3]);
          if (isNaN(cx) || isNaN(cy) || isNaN(cz)) {
            response = `/tell ${username} X Y Z values must be numeric integers`;
            break;
          }
          if (tpind < 0) {
            userDB[uind].tp.push({
              name: name,
              dimension: dn,
              coordinates: {
                x: cx,
                y: cy,
                z: cz
              }
            });
            response = `/tell ${username} tp '${name}' in dimension '${d}' created`;
          } else {
            userDB[uind].tp[tpind].coordinates = {
              x: cx,
              y: cy,
              z: cz
            };
            response = `/tell ${username} tp '${name}' in dimension '${d}' updated`;
          }
          // console.log('good')
          break;
        default:
          response = `/tell ${username} Wrong arguments, use !tp <name> <overworld|nether|end> [<x> <y> <z>]`;
          break;
      }
      // console.log('command pos')
      write(response);
      break;
    case 'gtp':
      response = '';
      const nameg = args[0].toUpperCase();
      const dg = args[1].toUpperCase();
      const idg = checkDimension(dg);
      if (idg < 0) {
        write(`/tell ${username} Dimension '${dg}' does not exists`);
        return;
      }
      const dng = dimensions[idg];
      const ind = tpDB.findIndex((tp: ITp) =>
        tp.name === nameg && tp.dimension === dng);
      switch (args.length) {
        case 1:
          // do tp if exists//
          if (ind < 0) {
            response = `/tell ${username} tp '${nameg}' on dimension '${dg}' does not exists`
            break;
          };
          const tpg = tpDB[ind].coordinates;
          // response = `/tp ${username} ${tpg.x} ${tpg.y} ${tpg.z}`;
          response = `/execute in ${dng} run tp ${username} ${tpg.x} ${tpg.y} ${tpg.z}`;
          break;
        case 5:
          // store / update global tp //
          const cxg = parseInt(args[2]);
          const cyg = parseInt(args[3]);
          const czg = parseInt(args[4]);
          if (isNaN(cxg) || isNaN(cyg) || isNaN(czg)) {
            response = `/tell ${username} X Y Z values must be numeric integers`;
            break;
          }
          if (ind < 0) {
            tpDB.push({
              name: nameg,
              dimension: dng,
              coordinates: {
                x: cxg,
                y: cyg,
                z: czg
              }
            });
            // response = `/tell ${username} tp '${nameg}' created`;
            response = `Global tp '${nameg}' on dimension '${dg}' created`;
          } else {
            tpDB[ind].coordinates = {
              x: cxg,
              y: cyg,
              z: czg
            };
            // response = `/tell ${username} tp '${nameg}' updated`;
            response = `Global tp '${nameg}' on dimension '${dg}' updated`;
          }
          // console.log('good')
          break;
        default:
          response = `/tell ${username} Wrong arguments: !tpg <name> <overworld|nether|end> [ <x> <y> <z> ]`;
          break;
      }
      if (response.length === 0) return;
      write(response);
      break;
    case 'disconnect':
      /* check for op */
      if (username !== 'arceus6666') break;
      write('Disconnecting');
      setTimeout(() => {
        client.end('op close');
      }, 5000);
      break;
    case 'print':
      console.log(jsonMsg);
      break;
    case 'packet':
      console.log(packet);
      break;
    case 'env':
      console.log(process.env);
    case 'test':
      write('testing');
      break;
    default:
      write(`/tell ${username} Unknown command, use !list or !help for more details`);
      break;
  }
});
client.on('end', (reason) => {
  console.log('something:', reason);
  clearInterval(timer);
  process.exit(0)
});

Expected behavior

Client stay logged in

Additional context

TypeError: Deserialization error for play.toClient : Read error for undefined : Cannot destructure property 'value' of 'ctx.entityMetadataItem(...)' as it is undefined.
at eval (eval at compile (\girabot\node_modules\protodef\src\compiler.js:243:12), :199:24)
at Object.entityMetadata (eval at compile (r\girabot\node_modules\protodef\src\compiler.js:243:12), :201:9)
at Object.packet_entity_metadata (eval at compile (\girabot\node_modules\protodef\src\compiler.js:243:12), :1392:75)
at eval (eval at compile (\girabot\node_modules\protodef\src\compiler.js:243:12), :1947:70)
at packet (eval at compile (\girabot\node_modules\protodef\src\compiler.js:243:12), :1974:9)
at CompiledProtodef.read (\girabot\node_modules\protodef\src\compiler.js:64:12)
at e.message (\girabot\node_modules\protodef\src\compiler.js:99:49)
at tryCatch (\girabot\node_modules\protodef\src\utils.js:50:16)
at CompiledProtodef.parsePacketBuffer (\girabot\node_modules\protodef\src\compiler.js:99:29)
at FullPacketParser.parsePacketBuffer (\girabot\node_modules\protodef\src\serializer.js:68:23)
Emitted 'error' event on Client instance at:
at FullPacketParser. (\girabot\node_modules\minecraft-protocol\src\client.js:77:12)
eam_writable.js:160:5)
at FullPacketParser.afterTransform (\girabot\node_modules\readable-stream\lib_stream_transform.js:89:3)
at FullPacketParser._transform (\girabot\node_modules\protodef\src\serializer.js:80:14)
at FullPacketParser.Transform._read (\girabot\node_modules\readable-stream\lib_stream_transform.js:177:10)
at FullPacketParser.Transform._write (\girabot\node_modules\readable-stream\lib_stream_transform.js:164:83) {
field: 'play.toClient'
}

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the failure with minecraft-protocol 1.13.0, Node 12.16.0, and a 1.16.1 vanilla, Spigot, or Paper server. Start at the packet_entity_metadata and entityMetadata stack frames, then inspect src/client.js where the parser error is emitted. Done means the client stays logged in when another player flies over a jungle biome without this deserialization error.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
networking
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.