Initial commit
This commit is contained in:
commit
e7a2cbc006
14 changed files with 2107 additions and 0 deletions
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
*.dem binary
|
104
.gitignore
vendored
Normal file
104
.gitignore
vendored
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 nullprop
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
10
README.md
Normal file
10
README.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
## demo-interp
|
||||||
|
Interpolate demo files by filling out gaps between Packets.
|
||||||
|
Normally a demo has a Packet every 4 ticks, this fills out the ticks inbetween by interpolating between the Packets.
|
||||||
|
Makes a visible difference on low `cl_interp` values at least.
|
||||||
|
|
||||||
|
## usage
|
||||||
|
`yarn install`
|
||||||
|
`yarn build`
|
||||||
|
`cd test`
|
||||||
|
`node test-interp.js`
|
277
build/index.js
Normal file
277
build/index.js
Normal file
|
@ -0,0 +1,277 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const bit_buffer_1 = require("bit-buffer");
|
||||||
|
const fs_1 = require("fs");
|
||||||
|
const DynamicBitStream_1 = require("@demostf/demo.js/build/DynamicBitStream");
|
||||||
|
const Message_1 = require("@demostf/demo.js/build/Data/Message");
|
||||||
|
const Parser_1 = require("@demostf/demo.js/build/Parser");
|
||||||
|
const Encoder_1 = require("@demostf/demo.js/build/Encoder");
|
||||||
|
function lerp(start, end, amount) {
|
||||||
|
return start + (end - start) * amount;
|
||||||
|
}
|
||||||
|
function circleLerp(start, end, amount) {
|
||||||
|
var shortestAngle = ((((end - start) % 360) + 540) % 360) - 180;
|
||||||
|
return start + (shortestAngle * amount) % 360;
|
||||||
|
}
|
||||||
|
function interpEntity(start, end, amount) {
|
||||||
|
var names = [
|
||||||
|
"m_vecOrigin",
|
||||||
|
"m_vecOrigin[2]",
|
||||||
|
"m_angRotation",
|
||||||
|
"m_angEyeAngles[0]",
|
||||||
|
"m_angEyeAngles[1]",
|
||||||
|
"m_angCustomModelRotation",
|
||||||
|
"m_vecPunchAngle",
|
||||||
|
"m_vecViewOffset[0]",
|
||||||
|
"m_vecViewOffset[1]",
|
||||||
|
"m_vecViewOffset[2]",
|
||||||
|
"m_vecBaseVelocity",
|
||||||
|
"m_vecVelocity[0]",
|
||||||
|
"m_vecVelocity[1]",
|
||||||
|
"m_vecVelocity[2]",
|
||||||
|
"m_vecMins",
|
||||||
|
"m_vecMaxs",
|
||||||
|
"m_vecSpecifiedSurroundingMinsPreScaled",
|
||||||
|
"m_vecSpecifiedSurroundingMaxsPreScaled",
|
||||||
|
"m_vecSpecifiedSurroundingMins",
|
||||||
|
"m_vecSpecifiedSurroundingMaxs",
|
||||||
|
"m_vecMinsPreScaled",
|
||||||
|
"m_vecMaxsPreScaled",
|
||||||
|
"m_vecConstraintCenter",
|
||||||
|
"m_vecCustomModelOffset",
|
||||||
|
"m_vecForce",
|
||||||
|
];
|
||||||
|
// numbers
|
||||||
|
for (let prop1 of start.props) {
|
||||||
|
if (!names.includes(prop1.definition.name))
|
||||||
|
continue;
|
||||||
|
if (typeof prop1.value !== "number")
|
||||||
|
continue;
|
||||||
|
for (let prop2 of end.props) {
|
||||||
|
if (!names.includes(prop2.definition.name))
|
||||||
|
continue;
|
||||||
|
if (typeof prop2.value !== "number")
|
||||||
|
continue;
|
||||||
|
if (prop1.definition.name !== prop2.definition.name)
|
||||||
|
continue;
|
||||||
|
if (prop1.definition.table !== prop2.definition.table)
|
||||||
|
continue;
|
||||||
|
if (prop1.definition.ownerTableName !== prop2.definition.ownerTableName)
|
||||||
|
continue;
|
||||||
|
if (prop1.definition.bitCount !== prop2.definition.bitCount)
|
||||||
|
continue;
|
||||||
|
if (prop1.definition.flags !== prop2.definition.flags)
|
||||||
|
continue;
|
||||||
|
if (prop1.definition.lowValue === 0 && prop1.definition.highValue === 360) {
|
||||||
|
// angles clamped to 0-360
|
||||||
|
//console.log(`Circle: ${prop1.definition.name} (${prop1.value})`);
|
||||||
|
prop1.value = circleLerp(prop1.value, prop2.value, amount);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//console.log(`Linear: ${prop1.definition.name} (${prop1.value})`);
|
||||||
|
prop1.value = lerp(prop1.value, prop2.value, amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// vectors
|
||||||
|
var keys = ["x", "y", "z"];
|
||||||
|
for (let prop1 of start.props) {
|
||||||
|
if (!names.includes(prop1.definition.name))
|
||||||
|
continue;
|
||||||
|
if (typeof prop1.value !== "object")
|
||||||
|
continue;
|
||||||
|
if (Object.keys(prop1.value).length !== 3)
|
||||||
|
continue;
|
||||||
|
var cont = false;
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
if (!Object.keys(prop1.value).includes(keys[i])) {
|
||||||
|
cont = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cont)
|
||||||
|
continue;
|
||||||
|
for (let prop2 of end.props) {
|
||||||
|
if (!names.includes(prop2.definition.name))
|
||||||
|
continue;
|
||||||
|
if (typeof prop2.value !== "object")
|
||||||
|
continue;
|
||||||
|
if (Object.keys(prop2.value).length !== 3)
|
||||||
|
continue;
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
if (!Object.keys(prop2.value).includes(keys[i])) {
|
||||||
|
cont = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cont)
|
||||||
|
continue;
|
||||||
|
if (prop1.definition.name !== prop2.definition.name)
|
||||||
|
continue;
|
||||||
|
if (prop1.definition.table !== prop2.definition.table)
|
||||||
|
continue;
|
||||||
|
if (prop1.definition.ownerTableName !== prop2.definition.ownerTableName)
|
||||||
|
continue;
|
||||||
|
if (prop1.definition.bitCount !== prop2.definition.bitCount)
|
||||||
|
continue;
|
||||||
|
if (prop1.definition.flags !== prop2.definition.flags)
|
||||||
|
continue;
|
||||||
|
for (const key of keys) {
|
||||||
|
prop1.value[key] = lerp(prop1.value[key], prop2.value[key], amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
function incrementEntityTicks(entity, amount) {
|
||||||
|
var names = [
|
||||||
|
"m_nTickBase",
|
||||||
|
"m_flSimulationTime"
|
||||||
|
];
|
||||||
|
for (let prop of entity.props) {
|
||||||
|
if (names.includes(prop.definition.name)) {
|
||||||
|
prop.value = prop.value + amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
class InterpTransformer extends Parser_1.Parser {
|
||||||
|
constructor(sourceStream, targetStream) {
|
||||||
|
super(sourceStream);
|
||||||
|
this.encoder = new Encoder_1.Encoder(targetStream);
|
||||||
|
}
|
||||||
|
writeMessage(message) {
|
||||||
|
this.parserState.handleMessage(message);
|
||||||
|
if (message.type === Message_1.MessageType.Packet) {
|
||||||
|
for (const packet of message.packets) {
|
||||||
|
this.parserState.handlePacket(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.encoder.writeMessage(message);
|
||||||
|
}
|
||||||
|
transform() {
|
||||||
|
const header = this.getHeader();
|
||||||
|
header.frames *= 4;
|
||||||
|
//console.log(header);
|
||||||
|
this.encoder.encodeHeader(header);
|
||||||
|
var prevProgressPrint = 0;
|
||||||
|
var prevPacketMessage = null;
|
||||||
|
var synced = false;
|
||||||
|
var skippedFirst = false;
|
||||||
|
for (let message of this.iterateMessages()) {
|
||||||
|
if (message.type === Message_1.MessageType.SyncTick) {
|
||||||
|
// <Header>
|
||||||
|
// <Packet>
|
||||||
|
// <DataTables>
|
||||||
|
// <StringTables>
|
||||||
|
// <Packet>
|
||||||
|
// <Packet>
|
||||||
|
// <SyncTick>
|
||||||
|
// <Packet> | tick 4, the fat network packet - probably contains initial states of everything
|
||||||
|
// <Packet> | tick 8, delta packets start here?; i think we only want to manipulate these ones
|
||||||
|
// ...
|
||||||
|
// <Stop>
|
||||||
|
synced = true;
|
||||||
|
}
|
||||||
|
if (synced && skippedFirst && message.type === Message_1.MessageType.Packet) {
|
||||||
|
if (prevPacketMessage) {
|
||||||
|
message.sequenceIn = prevPacketMessage.sequenceIn + 4;
|
||||||
|
message.sequenceOut = prevPacketMessage.sequenceOut + 4;
|
||||||
|
prevPacketMessage.packets = prevPacketMessage.packets.filter((value, index, arr) => {
|
||||||
|
//if (!["netTick", "packetEntities"].includes(value.packetType)) console.log(`REMOVING ${value.packetType}`);
|
||||||
|
return ["netTick", "packetEntities"].includes(value.packetType);
|
||||||
|
});
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
prevPacketMessage.tick++;
|
||||||
|
prevPacketMessage.sequenceIn++;
|
||||||
|
prevPacketMessage.sequenceOut++;
|
||||||
|
for (let packet of prevPacketMessage.packets) {
|
||||||
|
// FIXME: chat messages are duplicated 4 times,
|
||||||
|
// should probably remove chat packets here.
|
||||||
|
// Figure out if we can remove other types too.
|
||||||
|
if (packet.packetType === "netTick") {
|
||||||
|
packet.tick++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (packet.packetType === "packetEntities") {
|
||||||
|
for (let entity of packet.entities) {
|
||||||
|
// if (entity.entityIndex > 64) {
|
||||||
|
// // max players
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// if (entity.serverClass.name !== "CTFPlayer") {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// Loop through new message and interp
|
||||||
|
for (let newPacket of message.packets) {
|
||||||
|
if (newPacket.packetType !== "packetEntities") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (let newEntity of newPacket.entities) {
|
||||||
|
// if (newEntity.entityIndex > 64) {
|
||||||
|
// // max players
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// if (newEntity.serverClass.name !== "CTFPlayer") {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
if (newEntity.entityIndex !== entity.entityIndex) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// We interpolate same entity multiple times,
|
||||||
|
// edit interp amount appropriately.
|
||||||
|
// TODO: figure out formula for this...
|
||||||
|
// 0 -> 0.25 = 0.25
|
||||||
|
// 0.25 -> 0.5 = 1/3
|
||||||
|
// 0.5 -> 0.75 = 0.5
|
||||||
|
var interp = 0.25;
|
||||||
|
if (i == 1)
|
||||||
|
interp = 1 / 3;
|
||||||
|
else if (i == 2)
|
||||||
|
interp = 0.5;
|
||||||
|
entity = interpEntity(entity, newEntity, interp);
|
||||||
|
entity = incrementEntityTicks(entity, 1);
|
||||||
|
newEntity = incrementEntityTicks(newEntity, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//console.log(`interp tick: ${prevPacketMessage.tick}, in: ${prevPacketMessage.sequenceIn}, out: ${prevPacketMessage.sequenceOut}`);
|
||||||
|
this.writeMessage(prevPacketMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prevPacketMessage = message;
|
||||||
|
if (message.tick > prevProgressPrint + 10000) {
|
||||||
|
prevProgressPrint += 10000;
|
||||||
|
console.log(`Progress: ${message.tick} / ${header.ticks}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (synced && !skippedFirst && message.type === Message_1.MessageType.Packet) {
|
||||||
|
// Skip first packet
|
||||||
|
skippedFirst = true;
|
||||||
|
}
|
||||||
|
// if (message.type === MessageType.Packet) {
|
||||||
|
// console.log(`tick: ${message.tick}, in: ${message.sequenceIn}, out: ${message.sequenceOut}`);
|
||||||
|
// }
|
||||||
|
// Write current message
|
||||||
|
this.writeMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Interpolate demo files by filling out the gaps between Packets.
|
||||||
|
* @param input - Input demofile name
|
||||||
|
* @param output - Output demofile name
|
||||||
|
*/
|
||||||
|
function interp(input, output) {
|
||||||
|
const decodeStream = new bit_buffer_1.BitStream(fs_1.readFileSync(input).buffer);
|
||||||
|
const encodeStream = new DynamicBitStream_1.DynamicBitStream(32 * 1024 * 1024);
|
||||||
|
const transformer = new InterpTransformer(decodeStream, encodeStream);
|
||||||
|
transformer.transform();
|
||||||
|
const encodedLength = encodeStream.index;
|
||||||
|
encodeStream.index = 0;
|
||||||
|
fs_1.writeFileSync(output, encodeStream.readArrayBuffer(Math.ceil(encodedLength / 8)));
|
||||||
|
}
|
||||||
|
exports.interp = interp;
|
||||||
|
//# sourceMappingURL=index.js.map
|
1
build/index.js.map
Normal file
1
build/index.js.map
Normal file
File diff suppressed because one or more lines are too long
16
package.json
Normal file
16
package.json
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "demo-interp",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@demostf/demo.js": "^2.1.1",
|
||||||
|
"@types/node": "^13.1.7",
|
||||||
|
"patch-package": "^6.2.0",
|
||||||
|
"postinstall-postinstall": "^2.0.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"postinstall": "patch-package",
|
||||||
|
"build": "tsc"
|
||||||
|
}
|
||||||
|
}
|
16
patches/@demostf+demo.js+2.1.1.patch
Normal file
16
patches/@demostf+demo.js+2.1.1.patch
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
diff --git a/node_modules/@demostf/demo.js/build/Parser/UserMessage/SayText2.js b/node_modules/@demostf/demo.js/build/Parser/UserMessage/SayText2.js
|
||||||
|
index 4dc36c3..7a294c7 100644
|
||||||
|
--- a/node_modules/@demostf/demo.js/build/Parser/UserMessage/SayText2.js
|
||||||
|
+++ b/node_modules/@demostf/demo.js/build/Parser/UserMessage/SayText2.js
|
||||||
|
@@ -4,9 +4,9 @@ function ParseSayText2(stream) {
|
||||||
|
const client = stream.readUint8();
|
||||||
|
const raw = stream.readUint8();
|
||||||
|
const pos = stream.index;
|
||||||
|
- let from;
|
||||||
|
+ let from = 'Server';
|
||||||
|
let text;
|
||||||
|
- let kind;
|
||||||
|
+ let kind = 'TF_Chat_AllSpec';
|
||||||
|
if (stream.readUint8() === 1) {
|
||||||
|
const first = stream.readUint8();
|
||||||
|
if (first === 7) {
|
298
src/index.ts
Normal file
298
src/index.ts
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
import { BitStream } from 'bit-buffer';
|
||||||
|
import { readFileSync, writeFileSync } from 'fs';
|
||||||
|
import { DynamicBitStream } from '@demostf/demo.js/build/DynamicBitStream';
|
||||||
|
import { Message, MessageType, PacketMessage } from '@demostf/demo.js/build/Data/Message';
|
||||||
|
import { Parser } from '@demostf/demo.js/build/Parser';
|
||||||
|
import { Encoder } from '@demostf/demo.js/build/Encoder';
|
||||||
|
import { PacketEntity } from '@demostf/demo.js/build/Data/PacketEntity';
|
||||||
|
|
||||||
|
function lerp(start: number, end: number, amount: number) {
|
||||||
|
return start + (end - start) * amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function circleLerp(start: number, end: number, amount: number) {
|
||||||
|
var shortestAngle = ((((end - start) % 360) + 540) % 360) - 180;
|
||||||
|
return start + (shortestAngle * amount) % 360;
|
||||||
|
}
|
||||||
|
|
||||||
|
function interpEntity(start: PacketEntity, end: PacketEntity, amount: number): PacketEntity {
|
||||||
|
var names = [
|
||||||
|
"m_vecOrigin",
|
||||||
|
"m_vecOrigin[2]",
|
||||||
|
"m_angRotation",
|
||||||
|
"m_angEyeAngles[0]",
|
||||||
|
"m_angEyeAngles[1]",
|
||||||
|
"m_angCustomModelRotation",
|
||||||
|
"m_vecPunchAngle",
|
||||||
|
"m_vecViewOffset[0]",
|
||||||
|
"m_vecViewOffset[1]",
|
||||||
|
"m_vecViewOffset[2]",
|
||||||
|
"m_vecBaseVelocity",
|
||||||
|
"m_vecVelocity[0]",
|
||||||
|
"m_vecVelocity[1]",
|
||||||
|
"m_vecVelocity[2]",
|
||||||
|
"m_vecMins",
|
||||||
|
"m_vecMaxs",
|
||||||
|
"m_vecSpecifiedSurroundingMinsPreScaled",
|
||||||
|
"m_vecSpecifiedSurroundingMaxsPreScaled",
|
||||||
|
"m_vecSpecifiedSurroundingMins",
|
||||||
|
"m_vecSpecifiedSurroundingMaxs",
|
||||||
|
"m_vecMinsPreScaled",
|
||||||
|
"m_vecMaxsPreScaled",
|
||||||
|
"m_vecConstraintCenter",
|
||||||
|
"m_vecCustomModelOffset",
|
||||||
|
"m_vecForce",
|
||||||
|
];
|
||||||
|
|
||||||
|
// numbers
|
||||||
|
for (let prop1 of start.props) {
|
||||||
|
if (!names.includes(prop1.definition.name)) continue;
|
||||||
|
if (typeof prop1.value !== "number") continue;
|
||||||
|
|
||||||
|
for (let prop2 of end.props) {
|
||||||
|
if (!names.includes(prop2.definition.name)) continue;
|
||||||
|
if (typeof prop2.value !== "number") continue;
|
||||||
|
|
||||||
|
if (prop1.definition.name !== prop2.definition.name) continue;
|
||||||
|
if (prop1.definition.table !== prop2.definition.table) continue;
|
||||||
|
if (prop1.definition.ownerTableName !== prop2.definition.ownerTableName) continue;
|
||||||
|
if (prop1.definition.bitCount !== prop2.definition.bitCount) continue;
|
||||||
|
if (prop1.definition.flags !== prop2.definition.flags) continue;
|
||||||
|
|
||||||
|
if (prop1.definition.lowValue === 0 && prop1.definition.highValue === 360) {
|
||||||
|
// angles clamped to 0-360
|
||||||
|
//console.log(`Circle: ${prop1.definition.name} (${prop1.value})`);
|
||||||
|
prop1.value = circleLerp(prop1.value as number, prop2.value as number, amount);
|
||||||
|
} else {
|
||||||
|
//console.log(`Linear: ${prop1.definition.name} (${prop1.value})`);
|
||||||
|
prop1.value = lerp(prop1.value as number, prop2.value as number, amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// vectors
|
||||||
|
var keys = ["x", "y", "z"];
|
||||||
|
for (let prop1 of start.props) {
|
||||||
|
if (!names.includes(prop1.definition.name)) continue;
|
||||||
|
if (typeof prop1.value !== "object") continue;
|
||||||
|
if (Object.keys(prop1.value).length !== 3) continue;
|
||||||
|
|
||||||
|
var cont = false;
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
if (!Object.keys(prop1.value).includes(keys[i])) {
|
||||||
|
cont = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cont) continue;
|
||||||
|
|
||||||
|
for (let prop2 of end.props) {
|
||||||
|
if (!names.includes(prop2.definition.name)) continue;
|
||||||
|
if (typeof prop2.value !== "object") continue;
|
||||||
|
if (Object.keys(prop2.value).length !== 3) continue;
|
||||||
|
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
if (!Object.keys(prop2.value).includes(keys[i])) {
|
||||||
|
cont = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cont) continue;
|
||||||
|
|
||||||
|
if (prop1.definition.name !== prop2.definition.name) continue;
|
||||||
|
if (prop1.definition.table !== prop2.definition.table) continue;
|
||||||
|
if (prop1.definition.ownerTableName !== prop2.definition.ownerTableName) continue;
|
||||||
|
if (prop1.definition.bitCount !== prop2.definition.bitCount) continue;
|
||||||
|
if (prop1.definition.flags !== prop2.definition.flags) continue;
|
||||||
|
|
||||||
|
for (const key of keys) {
|
||||||
|
prop1.value[key] = lerp(prop1.value[key] as number, prop2.value[key] as number, amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
function incrementEntityTicks(entity: PacketEntity, amount: number): PacketEntity {
|
||||||
|
var names = [
|
||||||
|
"m_nTickBase",
|
||||||
|
"m_flSimulationTime"
|
||||||
|
];
|
||||||
|
for (let prop of entity.props) {
|
||||||
|
if (names.includes(prop.definition.name)) {
|
||||||
|
prop.value = prop.value as number + amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
class InterpTransformer extends Parser {
|
||||||
|
private readonly encoder: Encoder;
|
||||||
|
|
||||||
|
constructor(sourceStream: BitStream, targetStream: BitStream) {
|
||||||
|
super(sourceStream);
|
||||||
|
this.encoder = new Encoder(targetStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeMessage(message: Message) {
|
||||||
|
this.parserState.handleMessage(message);
|
||||||
|
if (message.type === MessageType.Packet) {
|
||||||
|
for (const packet of message.packets) {
|
||||||
|
this.parserState.handlePacket(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.encoder.writeMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public transform() {
|
||||||
|
const header = this.getHeader();
|
||||||
|
header.frames *= 4;
|
||||||
|
//console.log(header);
|
||||||
|
this.encoder.encodeHeader(header);
|
||||||
|
|
||||||
|
var prevProgressPrint = 0;
|
||||||
|
var prevPacketMessage = null;
|
||||||
|
var synced = false;
|
||||||
|
var skippedFirst = false;
|
||||||
|
|
||||||
|
for (let message of this.iterateMessages()) {
|
||||||
|
if (message.type === MessageType.SyncTick) {
|
||||||
|
// <Header>
|
||||||
|
// <Packet>
|
||||||
|
// <DataTables>
|
||||||
|
// <StringTables>
|
||||||
|
// <Packet>
|
||||||
|
// <Packet>
|
||||||
|
// <SyncTick>
|
||||||
|
// <Packet> | tick 4, the fat network packet - probably contains initial states of everything
|
||||||
|
// <Packet> | tick 8, delta packets start here?; i think we only want to manipulate these ones
|
||||||
|
// ...
|
||||||
|
// <Stop>
|
||||||
|
synced = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (synced && skippedFirst && message.type === MessageType.Packet) {
|
||||||
|
if (prevPacketMessage) {
|
||||||
|
message.sequenceIn = prevPacketMessage.sequenceIn + 4;
|
||||||
|
message.sequenceOut = prevPacketMessage.sequenceOut + 4;
|
||||||
|
|
||||||
|
prevPacketMessage.packets = prevPacketMessage.packets.filter((value, index, arr) => {
|
||||||
|
//if (!["netTick", "packetEntities"].includes(value.packetType)) console.log(`REMOVING ${value.packetType}`);
|
||||||
|
return ["netTick", "packetEntities"].includes(value.packetType);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
prevPacketMessage.tick++;
|
||||||
|
prevPacketMessage.sequenceIn++;
|
||||||
|
prevPacketMessage.sequenceOut++;
|
||||||
|
|
||||||
|
for (let packet of prevPacketMessage.packets) {
|
||||||
|
// FIXME: chat messages are duplicated 4 times,
|
||||||
|
// should probably remove chat packets here.
|
||||||
|
// Figure out if we can remove other types too.
|
||||||
|
|
||||||
|
|
||||||
|
if (packet.packetType === "netTick") {
|
||||||
|
packet.tick++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.packetType === "packetEntities") {
|
||||||
|
for (let entity of packet.entities) {
|
||||||
|
// if (entity.entityIndex > 64) {
|
||||||
|
// // max players
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (entity.serverClass.name !== "CTFPlayer") {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Loop through new message and interp
|
||||||
|
for (let newPacket of message.packets) {
|
||||||
|
if (newPacket.packetType !== "packetEntities") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let newEntity of newPacket.entities) {
|
||||||
|
// if (newEntity.entityIndex > 64) {
|
||||||
|
// // max players
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (newEntity.serverClass.name !== "CTFPlayer") {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (newEntity.entityIndex !== entity.entityIndex) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We interpolate same entity multiple times,
|
||||||
|
// edit interp amount appropriately.
|
||||||
|
// TODO: figure out formula for this...
|
||||||
|
// 0 -> 0.25 = 0.25
|
||||||
|
// 0.25 -> 0.5 = 1/3
|
||||||
|
// 0.5 -> 0.75 = 0.5
|
||||||
|
|
||||||
|
var interp = 0.25;
|
||||||
|
if (i == 1) interp = 1 / 3;
|
||||||
|
else if (i == 2) interp = 0.5;
|
||||||
|
|
||||||
|
entity = interpEntity(entity, newEntity, interp);
|
||||||
|
entity = incrementEntityTicks(entity, 1);
|
||||||
|
newEntity = incrementEntityTicks(newEntity, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//console.log(`interp tick: ${prevPacketMessage.tick}, in: ${prevPacketMessage.sequenceIn}, out: ${prevPacketMessage.sequenceOut}`);
|
||||||
|
this.writeMessage(prevPacketMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prevPacketMessage = message;
|
||||||
|
|
||||||
|
if (message.tick > prevProgressPrint + 10000) {
|
||||||
|
prevProgressPrint += 10000;
|
||||||
|
console.log(`Progress: ${message.tick} / ${header.ticks}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (synced && !skippedFirst && message.type === MessageType.Packet) {
|
||||||
|
// Skip first packet
|
||||||
|
skippedFirst = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (message.type === MessageType.Packet) {
|
||||||
|
// console.log(`tick: ${message.tick}, in: ${message.sequenceIn}, out: ${message.sequenceOut}`);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Write current message
|
||||||
|
this.writeMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interpolate demo files by filling out the gaps between Packets.
|
||||||
|
* @param input - Input demofile name
|
||||||
|
* @param output - Output demofile name
|
||||||
|
*/
|
||||||
|
function interp(input: string, output: string) {
|
||||||
|
const decodeStream = new BitStream(readFileSync(input).buffer as ArrayBuffer);
|
||||||
|
const encodeStream = new DynamicBitStream(32 * 1024 * 1024);
|
||||||
|
|
||||||
|
const transformer = new InterpTransformer(decodeStream, encodeStream);
|
||||||
|
transformer.transform();
|
||||||
|
|
||||||
|
const encodedLength = encodeStream.index;
|
||||||
|
encodeStream.index = 0;
|
||||||
|
|
||||||
|
writeFileSync(output, encodeStream.readArrayBuffer(Math.ceil(encodedLength / 8)));
|
||||||
|
}
|
||||||
|
|
||||||
|
export { interp };
|
3
test/test-interp.js
Normal file
3
test/test-interp.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
var interp = require('../build/index.js').interp;
|
||||||
|
|
||||||
|
interp('test.dem', 'test_out.dem');
|
BIN
test/test.dem
Normal file
BIN
test/test.dem
Normal file
Binary file not shown.
BIN
test/test_out.dem
Normal file
BIN
test/test_out.dem
Normal file
Binary file not shown.
9
tsconfig.json
Normal file
9
tsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6",
|
||||||
|
"module": "commonjs",
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "build",
|
||||||
|
"rootDir": "src"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue