UNPKG

teeworlds

Version:

Library for (ingame) teeworlds bots.

599 lines 23.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Snapshot = exports.items = void 0; const UUIDManager_1 = require("./UUIDManager"); const MsgUnpacker_1 = require("./MsgUnpacker"); var decoder = new TextDecoder('utf-8'); const ___itemAppendix = [ { "type_id": 0, "size": 0, "name": "obj_ex" }, { "type_id": 1, "size": 10, "name": "obj_player_input" }, { "type_id": 2, "size": 6, "name": "obj_projectile" }, { "type_id": 3, "size": 5, "name": "obj_laser" }, { "type_id": 4, "size": 4, "name": "obj_pickup" }, { "type_id": 5, "size": 3, "name": "obj_flag" }, { "type_id": 6, "size": 8, "name": "obj_game_info" }, { "type_id": 7, "size": 4, "name": "obj_game_data" }, { "type_id": 8, "size": 15, "name": "obj_character_core" }, { "type_id": 9, "size": 22, "name": "obj_character" }, { "type_id": 10, "size": 5, "name": "obj_player_info" }, { "type_id": 11, "size": 17, "name": "obj_client_info" }, { "type_id": 12, "size": 3, "name": "obj_spectator_info" }, { "type_id": 13, "size": 2, "name": "common" }, // event_common { "type_id": 14, "size": 2, "name": "explosion" }, // event_explosion { "type_id": 15, "size": 2, "name": "spawn" }, // event_spawn { "type_id": 16, "size": 2, "name": "hammerhit" }, // event_hammerhit { "type_id": 17, "size": 3, "name": "death" }, // event_death { "type_id": 18, "size": 3, "name": "sound_global" }, // event_sound_global { "type_id": 19, "size": 3, "name": "sound_world" }, // event_sound_world { "type_id": 20, "size": 3, "name": "damage_indicator" } // event_damage_indicator ]; const itemAppendix = [ 0, 10, 6, 5, 4, 3, 8, 4, 15, 22, 5, 17, 3, 2, 2, 2, 2, 3, 3, 3, 3, ]; var items; (function (items) { items[items["OBJ_EX"] = 0] = "OBJ_EX"; items[items["OBJ_PLAYER_INPUT"] = 1] = "OBJ_PLAYER_INPUT"; items[items["OBJ_PROJECTILE"] = 2] = "OBJ_PROJECTILE"; items[items["OBJ_LASER"] = 3] = "OBJ_LASER"; items[items["OBJ_PICKUP"] = 4] = "OBJ_PICKUP"; items[items["OBJ_FLAG"] = 5] = "OBJ_FLAG"; items[items["OBJ_GAME_INFO"] = 6] = "OBJ_GAME_INFO"; items[items["OBJ_GAME_DATA"] = 7] = "OBJ_GAME_DATA"; items[items["OBJ_CHARACTER_CORE"] = 8] = "OBJ_CHARACTER_CORE"; items[items["OBJ_CHARACTER"] = 9] = "OBJ_CHARACTER"; items[items["OBJ_PLAYER_INFO"] = 10] = "OBJ_PLAYER_INFO"; items[items["OBJ_CLIENT_INFO"] = 11] = "OBJ_CLIENT_INFO"; items[items["OBJ_SPECTATOR_INFO"] = 12] = "OBJ_SPECTATOR_INFO"; items[items["EVENT_COMMON"] = 13] = "EVENT_COMMON"; items[items["EVENT_EXPLOSION"] = 14] = "EVENT_EXPLOSION"; items[items["EVENT_SPAWN"] = 15] = "EVENT_SPAWN"; items[items["EVENT_HAMMERHIT"] = 16] = "EVENT_HAMMERHIT"; items[items["EVENT_DEATH"] = 17] = "EVENT_DEATH"; items[items["EVENT_SOUND_GLOBAL"] = 18] = "EVENT_SOUND_GLOBAL"; items[items["EVENT_SOUND_WORLD"] = 19] = "EVENT_SOUND_WORLD"; items[items["EVENT_DAMAGE_INDICATOR"] = 20] = "EVENT_DAMAGE_INDICATOR"; })(items || (exports.items = items = {})); class Snapshot { constructor(_client) { this.deltas = []; this.eSnapHolder = []; this.crc_errors = 0; this.uuid_manager = new UUIDManager_1.UUIDManager(32767, true); // snapshot max_type // https://github.com/ddnet/ddnet/blob/571b0b36de83d18f2524ee371fc3223d04b94135/datasrc/network.py#L236 this.supported_uuids = [ "my-own-object@heinrich5991.de", "character@netobj.ddnet.tw", // validate_size=False "player@netobj.ddnet.tw", "gameinfo@netobj.ddnet.tw", // validate_size=False "projectile@netobj.ddnet.tw", "laser@netobj.ddnet.tw", ]; this.client = _client; } IntsToStr(pInts) { var pIntz = []; // var pStr = '' for (let x of pInts) { // pStr += String.fromCharCode((((x) >> 24) & 0xff) - 128); pIntz.push((((x) >> 24) & 0xff) - 128); // pStr += String.fromCharCode((((x) >> 16) & 0xff) - 128); pIntz.push((((x) >> 16) & 0xff) - 128); // pStr += String.fromCharCode((((x) >> 8) & 0xff) - 128); pIntz.push((((x) >> 8) & 0xff) - 128); // pStr += String.fromCharCode(((x) & 0xff) - 128); pIntz.push(((x) & 0xff) - 128); } pIntz.splice(-1, 1); let pStr = decoder.decode(new Uint8Array(pIntz)); pStr = pStr.replace(/\0.*/g, ''); // Remove content from first null char to end. return pStr; } parseItem(data, Type, id) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; var _item = {}; if (Type >= 0x4000) { // offset uuid type if (((_a = this.uuid_manager.LookupType(Type)) === null || _a === void 0 ? void 0 : _a.name) == "my-own-object@heinrich5991.de") { _item = { m_Test: data[0] }; } else if (((_b = this.uuid_manager.LookupType(Type)) === null || _b === void 0 ? void 0 : _b.name) == "character@netobj.ddnet.tw") { _item = { m_Flags: data[0], m_FreezeEnd: data[1], m_Jumps: data[2], m_TeleCheckpoint: data[3], m_StrongWeakID: data[4], // # New data fields for jump display, freeze bar and ninja bar // # Default values indicate that these values should not be used m_JumpedTotal: (_c = data[5]) !== null && _c !== void 0 ? _c : null, m_NinjaActivationTick: (_d = data[6]) !== null && _d !== void 0 ? _d : null, m_FreezeStart: (_e = data[7]) !== null && _e !== void 0 ? _e : null, // # New data fields for improved target accuracy m_TargetX: (_f = data[8]) !== null && _f !== void 0 ? _f : null, m_TargetY: (_g = data[9]) !== null && _g !== void 0 ? _g : null, id: id }; } else if (((_h = this.uuid_manager.LookupType(Type)) === null || _h === void 0 ? void 0 : _h.name) == "player@netobj.ddnet.tw") { _item = { m_Flags: data[0], m_AuthLevel: data[1], id: id }; } else if (((_j = this.uuid_manager.LookupType(Type)) === null || _j === void 0 ? void 0 : _j.name) == "gameinfo@netobj.ddnet.tw") { _item = { m_Flags: data[0], m_Version: data[1], m_Flags2: data[2] }; } else if (((_k = this.uuid_manager.LookupType(Type)) === null || _k === void 0 ? void 0 : _k.name) == "projectile@netobj.ddnet.tw") { _item = { m_X: data[0], m_Y: data[1], m_Angle: data[2], m_Data: data[3], m_Type: data[3], m_StartTick: data[3] }; } else if (((_l = this.uuid_manager.LookupType(Type)) === null || _l === void 0 ? void 0 : _l.name) == "laser@netobj.ddnet.tw") { _item = { m_ToX: data[0], m_ToY: data[1], m_FromX: data[2], m_FromY: data[3], m_Owner: data[3], m_Type: data[3] }; } return _item; } switch (Type) { case items.OBJ_EX: break; case items.OBJ_PLAYER_INPUT: _item = { direction: data[0], target_x: data[1], target_y: data[2], jump: data[3], fire: data[4], hook: data[5], player_flags: data[6], wanted_weapon: data[7], next_weapon: data[8], prev_weapon: data[9], }; break; case items.OBJ_PROJECTILE: _item = { x: data[0], y: data[1], vel_x: data[2], vel_y: data[3], type_: data[4], start_tick: data[5], }; break; case items.OBJ_LASER: _item = { x: data[0], y: data[1], from_x: data[2], from_y: data[3], start_tick: data[4], }; break; case items.OBJ_PICKUP: _item = { x: data[0], y: data[1], type_: data[2], subtype: data[3], }; break; case items.OBJ_FLAG: _item = { x: data[0], y: data[1], team: data[2], }; break; case items.OBJ_GAME_INFO: _item = { game_flags: data[0], game_state_flags: data[1], round_start_tick: data[2], warmup_timer: data[3], score_limit: data[4], time_limit: data[5], round_num: data[6], round_current: data[7], }; break; case items.OBJ_GAME_DATA: _item = { teamscore_red: data[0], teamscore_blue: data[1], flag_carrier_red: data[2], flag_carrier_blue: data[3], }; break; case items.OBJ_CHARACTER_CORE: _item = { tick: data[0], x: data[1], y: data[2], vel_x: data[3], vel_y: data[4], angle: data[5], direction: data[6], jumped: data[7], hooked_player: data[8], hook_state: data[9], hook_tick: data[10], hook_x: data[11], hook_y: data[12], hook_dx: data[13], hook_dy: data[14], }; break; case items.OBJ_CHARACTER: _item = { character_core: { tick: data[0], x: data[1], y: data[2], vel_x: data[3], vel_y: data[4], angle: data[5], direction: data[6], jumped: data[7], hooked_player: data[8], hook_state: data[9], hook_tick: data[10], hook_x: data[11], hook_y: data[12], hook_dx: data[13], hook_dy: data[14], }, player_flags: data[15], health: data[16], armor: data[17], ammo_count: data[18], weapon: data[19], emote: data[20], attack_tick: data[21], client_id: id }; break; case items.OBJ_PLAYER_INFO: _item = { local: data[0], client_id: data[1], team: data[2], score: data[3], latency: data[4], }; break; case items.OBJ_CLIENT_INFO: _item = { name: this.IntsToStr([data[0], data[1], data[2], data[3]]), clan: this.IntsToStr([data[4], data[5], data[6]]), country: data[7], skin: this.IntsToStr([data[8], data[9], data[10], data[11], data[12], data[13]]), use_custom_color: Number(data.slice(14, 15)), color_body: Number(data.slice(15, 16)), color_feet: Number(data.slice(16, 17)), id: id }; break; case items.OBJ_SPECTATOR_INFO: _item = { spectator_id: data[0], x: data[1], y: data[2], }; break; case items.EVENT_COMMON: _item = { x: data[0], y: data[1], }; break; case items.EVENT_EXPLOSION: _item = { common: { x: data[0], y: data[1] } }; break; case items.EVENT_SPAWN: _item = { common: { x: data[0], y: data[1] } }; break; case items.EVENT_HAMMERHIT: _item = { common: { x: data[0], y: data[1] } }; break; case items.EVENT_DEATH: _item = { client_id: data[0], common: { x: data[1], y: data[2] } }; break; case items.EVENT_SOUND_GLOBAL: _item = { common: { x: data[0], y: data[1] }, sound_id: data[2] }; break; case items.EVENT_SOUND_WORLD: _item = { common: { x: data[0], y: data[1] }, sound_id: data[2] }; break; case items.EVENT_DAMAGE_INDICATOR: _item = { angle: data[0], common: { x: data[0], y: data[1] }, }; break; } return _item; } crc() { var checksum = 0; // this.eSnapHolder.forEach(snap => { // if (snap.ack == tick) // snap.Snapshot.Data.forEach(el => checksum += el); // }) this.deltas.forEach(snap => { // if (snap.ack == tick) snap.data.forEach(el => checksum += el); }); return checksum & 0xffffffff; } unpackSnapshot(snap, deltatick, recvTick, WantedCrc) { let unpacker = new MsgUnpacker_1.MsgUnpacker(snap); let deltaSnaps = []; if (deltatick == -1) { this.eSnapHolder = []; this.deltas = []; } else { this.eSnapHolder = this.eSnapHolder.filter(a => { if (a.ack == deltatick) deltaSnaps.push(a); return a.ack >= deltatick; }); } if (snap.length == 0) { // empty snap, copy old one into new ack this.eSnapHolder.forEach(snap => { if (snap.ack == deltatick) this.eSnapHolder.push({ Snapshot: snap.Snapshot, ack: recvTick }); }); return { items: [], recvTick: recvTick }; } let oldDeltas = this.deltas; this.deltas = []; /* key = (((type_id) << 16) | (id)) * key_to_id = ((key) & 0xffff) * key_to_type_id = ((key >> 16) & 0xffff) * https://github.com/heinrich5991/libtw2/blob/master/snapshot/src/ * https://github.com/heinrich5991/libtw2/blob/master/doc/snapshot.md */ var _events = []; let num_removed_items = unpacker.unpackInt(); let num_item_deltas = unpacker.unpackInt(); unpacker.unpackInt(); // _zero padding /*snapshot_delta: [ 4] num_removed_items [ 4] num_item_deltas [ 4] _zero [*4] removed_item_keys [ ] item_deltas */ var deleted = []; for (let i = 0; i < num_removed_items; i++) { let deleted_key = unpacker.unpackInt(); // removed_item_keys deleted.push(deleted_key); } /*item_delta: [ 4] type_id [ 4] id [ 4] _size [*4] data_delta*/ // let items: {'items': {'data': number[], 'type_id': number, 'id': number, 'key': number}[]/*, 'client_infos': client_info[], 'player_infos': player_info[]*/, lost: number} = {items: [],/* client_infos: client_infos, player_infos: player_infos,*/ lost: 0}; // let deltaSnaps = this.eSnapHolder.filter(a => a.ack === deltatick); if (deltaSnaps.length == 0 && deltatick >= 0) { return { items: [], recvTick: -1 }; } for (let i = 0; i < num_item_deltas; i++) { let type_id = unpacker.unpackInt(); let id = unpacker.unpackInt(); const key = (((type_id) << 16) | (id)); let _size; if (type_id > 0 && type_id < itemAppendix.length) { _size = itemAppendix[type_id]; } else _size = unpacker.unpackInt(); let data = []; for (let j = 0; j < _size; j++) { // if (unpacker.remaining.length > 0) { data[j] = (unpacker.unpackInt()); // } else console.log(_size, "???") } let changed = false; if (deltatick >= 0) { // let index = deltaSnaps.map(delta => delta.Snapshot.Key).indexOf(key) let delta = deltaSnaps.find(delta => delta.Snapshot.Key === key); if (delta !== undefined) { let out = UndiffItem(delta.Snapshot.Data, data); data = out; changed = true; } // else no previous, use new data } let parsed; if (type_id !== 0) { if (!changed) { let oldDelta = oldDeltas.find(delta => delta.key == key); if (oldDelta !== undefined && compareArrays(data, oldDelta.data)) { parsed = oldDelta.parsed; } else parsed = this.parseItem(data, type_id, id); } else parsed = this.parseItem(data, type_id, id); this.eSnapHolder.push({ Snapshot: { Data: data, Key: key }, ack: recvTick }); this.deltas.push({ data, key, id, type_id, parsed }); if (type_id >= items.EVENT_COMMON && type_id <= items.EVENT_DAMAGE_INDICATOR) { // this.client.SnapshotUnpacker. _events.push({ type_id, parsed }); // this.client.SnapshotUnpacker.emit(___itemAppendix[type_id].name, parsed); } } else { this.eSnapHolder.push({ Snapshot: { Data: data, Key: key }, ack: recvTick }); this.deltas.push({ data, key, id, type_id, parsed: {} }); let test = (int) => [(int >> 24) & 0xff, (int >> 16) & 0xff, (int >> 8) & 0xff, (int) & 0xff]; let test2 = (ints) => ints.map(a => test(a)).flat(); let targetUUID = Buffer.from(test2(data)); if (!this.uuid_manager.LookupType(id)) { this.supported_uuids.forEach((a, i) => { let uuid = (0, UUIDManager_1.createTwMD5Hash)(a); if (targetUUID.compare(uuid) == 0) { this.uuid_manager.RegisterName(a, id); this.supported_uuids.splice(i, 1); } }); } } } for (let newSnap of deltaSnaps) { if (deleted.includes(newSnap.Snapshot.Key)) { continue; } if (this.eSnapHolder.findIndex(a => a.ack == recvTick && a.Snapshot.Key == newSnap.Snapshot.Key) === -1) { // ugly copy new snap to eSnapHolder (if it isnt pushed already) this.eSnapHolder.push({ Snapshot: { Data: newSnap.Snapshot.Data, Key: newSnap.Snapshot.Key }, ack: recvTick }); let oldDelta = oldDeltas.find(delta => delta.key == newSnap.Snapshot.Key); if (oldDelta !== undefined && compareArrays(newSnap.Snapshot.Data, oldDelta.data)) { this.deltas.push(oldDelta); } else { this.deltas.push({ data: newSnap.Snapshot.Data, key: newSnap.Snapshot.Key, id: newSnap.Snapshot.Key & 0xffff, type_id: ((newSnap.Snapshot.Key >> 16) & 0xffff), parsed: this.parseItem(newSnap.Snapshot.Data, ((newSnap.Snapshot.Key >> 16) & 0xffff), ((newSnap.Snapshot.Key) & 0xffff)) }); } } } let _crc = this.crc(); if (_crc !== WantedCrc) { this.deltas = oldDeltas; this.crc_errors++; if (this.crc_errors > 5) { recvTick = -1; this.crc_errors = 0; this.eSnapHolder = []; this.deltas = []; } else { recvTick = deltatick; } } else if (this.crc_errors > 0) this.crc_errors--; _events.forEach(a => this.client.SnapshotUnpacker.emit(___itemAppendix[a.type_id].name, a.parsed)); return { items: this.deltas, recvTick }; } } exports.Snapshot = Snapshot; function compareArrays(first, second) { if (first.length !== second.length) return false; for (var i = 0; i < first.length; i++) { if (first[i] !== second[i]) return false; } return true; } function UndiffItem(oldItem, newItem) { let out = newItem; // if (JSON.stringify(newItem) === JSON.stringify(oldItem)) // return newItem; oldItem.forEach((a, i) => { if (a !== undefined && out[i] !== undefined) { out[i] += a; } else { out[i] = 0; } }); return out; } //# sourceMappingURL=snapshot.js.map