Overhaul interpolation, check for maxvel
This commit is contained in:
parent
bd140ea356
commit
28cf51a6b6
6 changed files with 483 additions and 279 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,3 +1,6 @@
|
||||||
|
# Tests
|
||||||
|
test/test_out.dem
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
|
|
309
build/index.js
309
build/index.js
|
@ -1,5 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.interp = void 0;
|
||||||
const bit_buffer_1 = require("bit-buffer");
|
const bit_buffer_1 = require("bit-buffer");
|
||||||
const fs_1 = require("fs");
|
const fs_1 = require("fs");
|
||||||
const DynamicBitStream_1 = require("@demostf/demo.js/build/DynamicBitStream");
|
const DynamicBitStream_1 = require("@demostf/demo.js/build/DynamicBitStream");
|
||||||
|
@ -13,15 +14,38 @@ function circleLerp(start, end, amount) {
|
||||||
var shortestAngle = ((((end - start) % 360) + 540) % 360) - 180;
|
var shortestAngle = ((((end - start) % 360) + 540) % 360) - 180;
|
||||||
return start + (shortestAngle * amount) % 360;
|
return start + (shortestAngle * amount) % 360;
|
||||||
}
|
}
|
||||||
function interpEntity(start, end, amount) {
|
function interpNumber(start, end, amount, lowValue, highValue) {
|
||||||
var names = [
|
if (lowValue === 0 && highValue === 360) {
|
||||||
|
// angles clamped to 0-360
|
||||||
|
start = circleLerp(start, end, amount);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
start = lerp(start, end, amount);
|
||||||
|
}
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
// Props to interpolate,
|
||||||
|
// most of these are probably not needed
|
||||||
|
const propNames = [
|
||||||
|
// These are in a bunch of tables
|
||||||
"m_vecOrigin",
|
"m_vecOrigin",
|
||||||
"m_vecOrigin[2]",
|
// DT_BaseEntity
|
||||||
"m_angRotation",
|
"m_angRotation",
|
||||||
|
"m_flElasticity",
|
||||||
|
"m_flShadowCastDistance",
|
||||||
|
// DT_BaseAnimating
|
||||||
|
"m_vecForce",
|
||||||
|
"m_flModelScale",
|
||||||
|
"m_flFadeScale",
|
||||||
|
// DT_BasePlayer
|
||||||
|
"m_flMaxspeed",
|
||||||
|
"m_flFOVTime",
|
||||||
|
// DT_TFLocalPlayerExclusive
|
||||||
|
// DT_TFNonLocalPlayerExclusive
|
||||||
|
"m_vecOrigin[2]",
|
||||||
"m_angEyeAngles[0]",
|
"m_angEyeAngles[0]",
|
||||||
"m_angEyeAngles[1]",
|
"m_angEyeAngles[1]",
|
||||||
"m_angCustomModelRotation",
|
// DT_LocalPlayerExclusive
|
||||||
"m_vecPunchAngle",
|
|
||||||
"m_vecViewOffset[0]",
|
"m_vecViewOffset[0]",
|
||||||
"m_vecViewOffset[1]",
|
"m_vecViewOffset[1]",
|
||||||
"m_vecViewOffset[2]",
|
"m_vecViewOffset[2]",
|
||||||
|
@ -29,95 +53,74 @@ function interpEntity(start, end, amount) {
|
||||||
"m_vecVelocity[0]",
|
"m_vecVelocity[0]",
|
||||||
"m_vecVelocity[1]",
|
"m_vecVelocity[1]",
|
||||||
"m_vecVelocity[2]",
|
"m_vecVelocity[2]",
|
||||||
|
// DT_LOCAL
|
||||||
|
"m_flDucktime",
|
||||||
|
"m_flJumpTime",
|
||||||
|
"m_flDuckJumpTime",
|
||||||
|
"m_flFallVelocity",
|
||||||
|
"m_flNextAttack",
|
||||||
|
"m_vecPunchAngle",
|
||||||
|
"m_vecPunchAngleVel",
|
||||||
|
// DT_TFPlayerShared
|
||||||
|
"m_flDuckTimer",
|
||||||
|
"m_flMovementStunTime",
|
||||||
|
"m_flFirstPrimaryAttack",
|
||||||
|
"m_flEnergyDrinkMeter",
|
||||||
|
"m_flHypeMeter",
|
||||||
|
"m_flChargeMeter",
|
||||||
|
"m_flInvisChangeCompleteTime",
|
||||||
|
"m_flCloakMeter",
|
||||||
|
"m_flSpyTranqBuffDuration",
|
||||||
|
"m_flRuneCharge",
|
||||||
|
"m_flHolsterAnimTime",
|
||||||
|
// DT_CollisionProperty
|
||||||
"m_vecMins",
|
"m_vecMins",
|
||||||
"m_vecMaxs",
|
"m_vecMaxs",
|
||||||
"m_vecSpecifiedSurroundingMinsPreScaled",
|
|
||||||
"m_vecSpecifiedSurroundingMaxsPreScaled",
|
|
||||||
"m_vecSpecifiedSurroundingMins",
|
|
||||||
"m_vecSpecifiedSurroundingMaxs",
|
|
||||||
"m_vecMinsPreScaled",
|
"m_vecMinsPreScaled",
|
||||||
"m_vecMaxsPreScaled",
|
"m_vecMaxsPreScaled",
|
||||||
"m_vecConstraintCenter",
|
"m_vecSpecifiedSurroundingMins",
|
||||||
"m_vecCustomModelOffset",
|
"m_vecSpecifiedSurroundingMaxs",
|
||||||
"m_vecForce",
|
"m_vecSpecifiedSurroundingMinsPreScaled",
|
||||||
];
|
"m_vecSpecifiedSurroundingMaxsPreScaled",
|
||||||
// numbers
|
];
|
||||||
|
const vecKeys = ["x", "y", "z"];
|
||||||
|
const EF_NOINTERP = 8;
|
||||||
|
function interpEntity(start, end, amount) {
|
||||||
for (let prop1 of start.props) {
|
for (let prop1 of start.props) {
|
||||||
if (!names.includes(prop1.definition.name))
|
if (!propNames.includes(prop1.definition.name))
|
||||||
continue;
|
|
||||||
if (typeof prop1.value !== "number")
|
|
||||||
continue;
|
continue;
|
||||||
for (let prop2 of end.props) {
|
for (let prop2 of end.props) {
|
||||||
if (!names.includes(prop2.definition.name))
|
if (!propNames.includes(prop2.definition.name))
|
||||||
continue;
|
continue;
|
||||||
if (typeof prop2.value !== "number")
|
if (prop1.definition.fullName !== prop2.definition.fullName)
|
||||||
continue;
|
continue;
|
||||||
if (prop1.definition.name !== prop2.definition.name)
|
if (typeof prop1.value === "number") {
|
||||||
continue;
|
prop1.value = interpNumber(prop1.value, prop2.value, amount, prop1.definition.lowValue, prop1.definition.highValue);
|
||||||
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
// Interp vectors
|
||||||
if (cont)
|
if (typeof prop1.value === "object") {
|
||||||
continue;
|
if (Object.keys(prop1.value).length !== 3)
|
||||||
for (let prop2 of end.props) {
|
|
||||||
if (!names.includes(prop2.definition.name))
|
|
||||||
continue;
|
|
||||||
if (typeof prop2.value !== "object")
|
|
||||||
continue;
|
continue;
|
||||||
if (Object.keys(prop2.value).length !== 3)
|
if (Object.keys(prop2.value).length !== 3)
|
||||||
continue;
|
continue;
|
||||||
for (var i = 0; i < keys.length; i++) {
|
// Check x,y,z keys
|
||||||
if (!Object.keys(prop2.value).includes(keys[i])) {
|
var cont = false;
|
||||||
|
for (const key of vecKeys) {
|
||||||
|
if (!Object.keys(prop1.value).includes(key) ||
|
||||||
|
!Object.keys(prop2.value).includes(key) ||
|
||||||
|
typeof prop1.value[key] !== "number" ||
|
||||||
|
typeof prop2.value[key] !== "number") {
|
||||||
cont = true;
|
cont = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cont)
|
if (cont)
|
||||||
continue;
|
continue;
|
||||||
if (prop1.definition.name !== prop2.definition.name)
|
for (const key of vecKeys) {
|
||||||
continue;
|
prop1.value[key] = interpNumber(prop1.value[key], prop2.value[key], amount, prop1.definition.lowValue, prop1.definition.highValue);
|
||||||
if (prop1.definition.table !== prop2.definition.table)
|
}
|
||||||
continue;
|
break;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,9 +139,10 @@ function incrementEntityTicks(entity, amount) {
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
class InterpTransformer extends Parser_1.Parser {
|
class InterpTransformer extends Parser_1.Parser {
|
||||||
constructor(sourceStream, targetStream) {
|
constructor(sourceStream, targetStream, maxVel = 3500) {
|
||||||
super(sourceStream);
|
super(sourceStream);
|
||||||
this.encoder = new Encoder_1.Encoder(targetStream);
|
this.encoder = new Encoder_1.Encoder(targetStream);
|
||||||
|
this.maxVel = maxVel;
|
||||||
}
|
}
|
||||||
writeMessage(message) {
|
writeMessage(message) {
|
||||||
this.parserState.handleMessage(message);
|
this.parserState.handleMessage(message);
|
||||||
|
@ -152,12 +156,13 @@ class InterpTransformer extends Parser_1.Parser {
|
||||||
transform() {
|
transform() {
|
||||||
const header = this.getHeader();
|
const header = this.getHeader();
|
||||||
header.frames *= 4;
|
header.frames *= 4;
|
||||||
//console.log(header);
|
|
||||||
this.encoder.encodeHeader(header);
|
this.encoder.encodeHeader(header);
|
||||||
var prevProgressPrint = 0;
|
var prevProgressPrint = 0;
|
||||||
var prevPacketMessage = null;
|
var prevPacketMessage = null;
|
||||||
var synced = false;
|
var synced = false;
|
||||||
var skippedFirst = false;
|
var skippedFirst = false;
|
||||||
|
var lastKnownProps = {};
|
||||||
|
var tickInterval = 0.015;
|
||||||
for (let message of this.iterateMessages()) {
|
for (let message of this.iterateMessages()) {
|
||||||
if (message.type === Message_1.MessageType.SyncTick) {
|
if (message.type === Message_1.MessageType.SyncTick) {
|
||||||
// <Header>
|
// <Header>
|
||||||
|
@ -178,66 +183,147 @@ class InterpTransformer extends Parser_1.Parser {
|
||||||
message.sequenceIn = prevPacketMessage.sequenceIn + 4;
|
message.sequenceIn = prevPacketMessage.sequenceIn + 4;
|
||||||
message.sequenceOut = prevPacketMessage.sequenceOut + 4;
|
message.sequenceOut = prevPacketMessage.sequenceOut + 4;
|
||||||
prevPacketMessage.packets = prevPacketMessage.packets.filter((value, index, arr) => {
|
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);
|
return ["netTick", "packetEntities"].includes(value.packetType);
|
||||||
});
|
});
|
||||||
|
// Packets only contain props if they change during that tick,
|
||||||
|
// store last known props and restore previously stored ones.
|
||||||
|
for (let p of prevPacketMessage.packets) {
|
||||||
|
if (p.packetType !== "packetEntities") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (let e of p.entities) {
|
||||||
|
if (p.removedEntities.includes(e.entityIndex)) {
|
||||||
|
//console.log("packet includes removed ent");
|
||||||
|
delete (lastKnownProps[`${e.entityIndex}:${e.serverClass.id}`]);
|
||||||
|
}
|
||||||
|
// Store from entity
|
||||||
|
for (const prop of e.props) {
|
||||||
|
if (!propNames.includes(prop.definition.name))
|
||||||
|
continue;
|
||||||
|
// Using just entityIndex will sometimes fail
|
||||||
|
// for some reason, maybe they are recycled.
|
||||||
|
// This happens even with the newPacket.removedEntities check
|
||||||
|
// later on...
|
||||||
|
if (lastKnownProps[`${e.entityIndex}:${e.serverClass.id}`] == null) {
|
||||||
|
lastKnownProps[`${e.entityIndex}:${e.serverClass.id}`] = {};
|
||||||
|
}
|
||||||
|
lastKnownProps[`${e.entityIndex}:${e.serverClass.id}`][prop.definition.fullName] = prop;
|
||||||
|
}
|
||||||
|
// Restore props later on after checking what props the future entities have
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add 3 intepolated ticks
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
prevPacketMessage.tick++;
|
prevPacketMessage.tick++;
|
||||||
prevPacketMessage.sequenceIn++;
|
prevPacketMessage.sequenceIn++;
|
||||||
prevPacketMessage.sequenceOut++;
|
prevPacketMessage.sequenceOut++;
|
||||||
for (let packet of prevPacketMessage.packets) {
|
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") {
|
if (packet.packetType === "netTick") {
|
||||||
packet.tick++;
|
packet.tick++;
|
||||||
|
tickInterval = packet.frameTime / 100000;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (packet.packetType === "packetEntities") {
|
// packetEntities
|
||||||
for (let entity of packet.entities) {
|
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
|
// Loop through new message and interp
|
||||||
for (let newPacket of message.packets) {
|
for (let newPacket of message.packets) {
|
||||||
if (newPacket.packetType !== "packetEntities") {
|
if (newPacket.packetType !== "packetEntities") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
for (const index of newPacket.removedEntities) {
|
||||||
|
for (let propIndex = Object.keys(lastKnownProps).length - 1; propIndex > 0; propIndex--) {
|
||||||
|
if (Object.keys(lastKnownProps)[propIndex].startsWith(index.toString() + ":")) {
|
||||||
|
delete (lastKnownProps[Object.keys(lastKnownProps)[propIndex]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newPacket.removedEntities.includes(entity.entityIndex)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
for (let newEntity of newPacket.entities) {
|
for (let newEntity of newPacket.entities) {
|
||||||
// if (newEntity.entityIndex > 64) {
|
|
||||||
// // max players
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// if (newEntity.serverClass.name !== "CTFPlayer") {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
if (newEntity.entityIndex !== entity.entityIndex) {
|
if (newEntity.entityIndex !== entity.entityIndex) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// We interpolate same entity multiple times,
|
if (newEntity.serverClass.id !== entity.serverClass.id) {
|
||||||
// edit interp amount appropriately.
|
continue;
|
||||||
// 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);
|
entity = incrementEntityTicks(entity, 1);
|
||||||
newEntity = incrementEntityTicks(newEntity, 1);
|
newEntity = incrementEntityTicks(newEntity, 1);
|
||||||
|
// Don't interp if new entity has EF_NOINTERP flag
|
||||||
|
var m_fEffects = newEntity.getPropValue("DT_BaseEntity.m_fEffects");
|
||||||
|
if (m_fEffects !== null) {
|
||||||
|
if ((m_fEffects & EF_NOINTERP) === EF_NOINTERP) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for maxVel
|
||||||
|
if (lastKnownProps[`${entity.entityIndex}:${entity.serverClass.id}`] != null) {
|
||||||
|
var lastOrigin = null;
|
||||||
|
var lastOriginZ = null;
|
||||||
|
var newOrigin = null;
|
||||||
|
var newOriginZ = null;
|
||||||
|
for (const p of newEntity.props) {
|
||||||
|
if (p.definition.name === "m_vecOrigin") {
|
||||||
|
newOrigin = p.value;
|
||||||
|
if (lastKnownProps[`${entity.entityIndex}:${entity.serverClass.id}`][p.definition.fullName] != null) {
|
||||||
|
lastOrigin = lastKnownProps[`${entity.entityIndex}:${entity.serverClass.id}`][p.definition.fullName].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (p.definition.name === "m_vecOrigin[2]") {
|
||||||
|
newOriginZ = p.value;
|
||||||
|
if (lastKnownProps[`${entity.entityIndex}:${entity.serverClass.id}`][p.definition.fullName] != null) {
|
||||||
|
lastOriginZ = lastKnownProps[`${entity.entityIndex}:${entity.serverClass.id}`][p.definition.fullName].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lastOrigin != null && newOrigin != null) {
|
||||||
|
if (lastOriginZ) {
|
||||||
|
lastOrigin.z = lastOriginZ;
|
||||||
|
}
|
||||||
|
if (newOriginZ) {
|
||||||
|
newOrigin.z = newOriginZ;
|
||||||
|
}
|
||||||
|
for (let axis of vecKeys) {
|
||||||
|
var dist = newOrigin[axis] - lastOrigin[axis];
|
||||||
|
if (dist > this.maxVel * tickInterval * 4) {
|
||||||
|
//console.log(`Entity ${newEntity.serverClass.name} (${newEntity.entityIndex}) moved over maxvel (${dist} > ${this.maxVel * tickInterval * 4})`);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Restore from previously stored props only the props
|
||||||
|
// that the future entity has, so we can interp between them.
|
||||||
|
// Only need to do this the first time,
|
||||||
|
// since we reuse the message.
|
||||||
|
if (i < 0) {
|
||||||
|
if (lastKnownProps[`${entity.entityIndex}:${entity.serverClass.id}`] != null) {
|
||||||
|
var propArr = [];
|
||||||
|
for (const propKey of Object.keys(lastKnownProps[`${entity.entityIndex}:${entity.serverClass.id}`])) {
|
||||||
|
// Check if new entity has this property,
|
||||||
|
// if not, no need to add it to the old entity for interpolation.
|
||||||
|
if (newEntity.hasProperty(propKey.split(".")[0], propKey.split(".")[1])) {
|
||||||
|
propArr.push(lastKnownProps[`${entity.entityIndex}:${entity.serverClass.id}`][propKey]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (propArr.length > 0) {
|
||||||
|
//console.log(`Applying ${propArr.length} props`);
|
||||||
|
entity.applyPropUpdate(propArr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We interpolate same entity multiple times,
|
||||||
|
// edit interp amount appropriately.
|
||||||
|
// min target max interp
|
||||||
|
// 0 0.25 1.0 0.25
|
||||||
|
// 0.25 0.5 1.0 1/3
|
||||||
|
// 0.5 0.75 1.0 0.5
|
||||||
|
var interp = 0.25 * (1 / (1 - (0.25 * i)));
|
||||||
|
entity = interpEntity(entity, newEntity, interp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//console.log(`interp tick: ${prevPacketMessage.tick}, in: ${prevPacketMessage.sequenceIn}, out: ${prevPacketMessage.sequenceOut}`);
|
|
||||||
this.writeMessage(prevPacketMessage);
|
this.writeMessage(prevPacketMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -251,9 +337,6 @@ class InterpTransformer extends Parser_1.Parser {
|
||||||
// Skip first packet
|
// Skip first packet
|
||||||
skippedFirst = true;
|
skippedFirst = true;
|
||||||
}
|
}
|
||||||
// if (message.type === MessageType.Packet) {
|
|
||||||
// console.log(`tick: ${message.tick}, in: ${message.sequenceIn}, out: ${message.sequenceOut}`);
|
|
||||||
// }
|
|
||||||
// Write current message
|
// Write current message
|
||||||
this.writeMessage(message);
|
this.writeMessage(message);
|
||||||
}
|
}
|
||||||
|
@ -264,10 +347,10 @@ class InterpTransformer extends Parser_1.Parser {
|
||||||
* @param input - Input demofile name
|
* @param input - Input demofile name
|
||||||
* @param output - Output demofile name
|
* @param output - Output demofile name
|
||||||
*/
|
*/
|
||||||
function interp(input, output) {
|
function interp(input, output, maxVel = 3500) {
|
||||||
const decodeStream = new bit_buffer_1.BitStream(fs_1.readFileSync(input).buffer);
|
const decodeStream = new bit_buffer_1.BitStream(fs_1.readFileSync(input).buffer);
|
||||||
const encodeStream = new DynamicBitStream_1.DynamicBitStream(32 * 1024 * 1024);
|
const encodeStream = new DynamicBitStream_1.DynamicBitStream(32 * 1024 * 1024);
|
||||||
const transformer = new InterpTransformer(decodeStream, encodeStream);
|
const transformer = new InterpTransformer(decodeStream, encodeStream, maxVel);
|
||||||
transformer.transform();
|
transformer.transform();
|
||||||
const encodedLength = encodeStream.index;
|
const encodedLength = encodeStream.index;
|
||||||
encodeStream.index = 0;
|
encodeStream.index = 0;
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "demo-interp",
|
"name": "demo-interp",
|
||||||
"version": "1.0.0",
|
"version": "0.1.0",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
332
src/index.ts
332
src/index.ts
|
@ -15,15 +15,43 @@ function circleLerp(start: number, end: number, amount: number) {
|
||||||
return start + (shortestAngle * amount) % 360;
|
return start + (shortestAngle * amount) % 360;
|
||||||
}
|
}
|
||||||
|
|
||||||
function interpEntity(start: PacketEntity, end: PacketEntity, amount: number): PacketEntity {
|
function interpNumber(start: number, end: number, amount: number, lowValue: number, highValue: number) {
|
||||||
var names = [
|
if (lowValue === 0 && highValue === 360) {
|
||||||
|
// angles clamped to 0-360
|
||||||
|
start = circleLerp(start, end, amount);
|
||||||
|
} else {
|
||||||
|
start = lerp(start, end, amount);
|
||||||
|
}
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Props to interpolate,
|
||||||
|
// most of these are probably not needed
|
||||||
|
const propNames = [
|
||||||
|
// These are in a bunch of tables
|
||||||
"m_vecOrigin",
|
"m_vecOrigin",
|
||||||
"m_vecOrigin[2]",
|
|
||||||
|
// DT_BaseEntity
|
||||||
"m_angRotation",
|
"m_angRotation",
|
||||||
|
"m_flElasticity",
|
||||||
|
"m_flShadowCastDistance",
|
||||||
|
|
||||||
|
// DT_BaseAnimating
|
||||||
|
"m_vecForce",
|
||||||
|
"m_flModelScale",
|
||||||
|
"m_flFadeScale",
|
||||||
|
|
||||||
|
// DT_BasePlayer
|
||||||
|
"m_flMaxspeed",
|
||||||
|
"m_flFOVTime",
|
||||||
|
|
||||||
|
// DT_TFLocalPlayerExclusive
|
||||||
|
// DT_TFNonLocalPlayerExclusive
|
||||||
|
"m_vecOrigin[2]",
|
||||||
"m_angEyeAngles[0]",
|
"m_angEyeAngles[0]",
|
||||||
"m_angEyeAngles[1]",
|
"m_angEyeAngles[1]",
|
||||||
"m_angCustomModelRotation",
|
|
||||||
"m_vecPunchAngle",
|
// DT_LocalPlayerExclusive
|
||||||
"m_vecViewOffset[0]",
|
"m_vecViewOffset[0]",
|
||||||
"m_vecViewOffset[1]",
|
"m_vecViewOffset[1]",
|
||||||
"m_vecViewOffset[2]",
|
"m_vecViewOffset[2]",
|
||||||
|
@ -31,82 +59,80 @@ function interpEntity(start: PacketEntity, end: PacketEntity, amount: number): P
|
||||||
"m_vecVelocity[0]",
|
"m_vecVelocity[0]",
|
||||||
"m_vecVelocity[1]",
|
"m_vecVelocity[1]",
|
||||||
"m_vecVelocity[2]",
|
"m_vecVelocity[2]",
|
||||||
|
|
||||||
|
// DT_LOCAL
|
||||||
|
"m_flDucktime",
|
||||||
|
"m_flJumpTime",
|
||||||
|
"m_flDuckJumpTime",
|
||||||
|
"m_flFallVelocity",
|
||||||
|
"m_flNextAttack",
|
||||||
|
"m_vecPunchAngle",
|
||||||
|
"m_vecPunchAngleVel",
|
||||||
|
|
||||||
|
// DT_TFPlayerShared
|
||||||
|
"m_flDuckTimer",
|
||||||
|
"m_flMovementStunTime",
|
||||||
|
"m_flFirstPrimaryAttack",
|
||||||
|
"m_flEnergyDrinkMeter",
|
||||||
|
"m_flHypeMeter",
|
||||||
|
"m_flChargeMeter",
|
||||||
|
"m_flInvisChangeCompleteTime",
|
||||||
|
"m_flCloakMeter",
|
||||||
|
"m_flSpyTranqBuffDuration",
|
||||||
|
"m_flRuneCharge",
|
||||||
|
"m_flHolsterAnimTime",
|
||||||
|
|
||||||
|
// DT_CollisionProperty
|
||||||
"m_vecMins",
|
"m_vecMins",
|
||||||
"m_vecMaxs",
|
"m_vecMaxs",
|
||||||
"m_vecSpecifiedSurroundingMinsPreScaled",
|
|
||||||
"m_vecSpecifiedSurroundingMaxsPreScaled",
|
|
||||||
"m_vecSpecifiedSurroundingMins",
|
|
||||||
"m_vecSpecifiedSurroundingMaxs",
|
|
||||||
"m_vecMinsPreScaled",
|
"m_vecMinsPreScaled",
|
||||||
"m_vecMaxsPreScaled",
|
"m_vecMaxsPreScaled",
|
||||||
"m_vecConstraintCenter",
|
"m_vecSpecifiedSurroundingMins",
|
||||||
"m_vecCustomModelOffset",
|
"m_vecSpecifiedSurroundingMaxs",
|
||||||
"m_vecForce",
|
"m_vecSpecifiedSurroundingMinsPreScaled",
|
||||||
];
|
"m_vecSpecifiedSurroundingMaxsPreScaled",
|
||||||
|
];
|
||||||
|
|
||||||
// numbers
|
const vecKeys = ["x", "y", "z"];
|
||||||
|
const EF_NOINTERP = 8
|
||||||
|
|
||||||
|
function interpEntity(start: PacketEntity, end: PacketEntity, amount: number): PacketEntity {
|
||||||
for (let prop1 of start.props) {
|
for (let prop1 of start.props) {
|
||||||
if (!names.includes(prop1.definition.name)) continue;
|
if (!propNames.includes(prop1.definition.name)) continue;
|
||||||
if (typeof prop1.value !== "number") continue;
|
|
||||||
|
|
||||||
for (let prop2 of end.props) {
|
for (let prop2 of end.props) {
|
||||||
if (!names.includes(prop2.definition.name)) continue;
|
if (!propNames.includes(prop2.definition.name)) continue;
|
||||||
if (typeof prop2.value !== "number") continue;
|
if (prop1.definition.fullName !== prop2.definition.fullName) continue;
|
||||||
|
|
||||||
if (prop1.definition.name !== prop2.definition.name) continue;
|
if (typeof prop1.value === "number") {
|
||||||
if (prop1.definition.table !== prop2.definition.table) continue;
|
prop1.value = interpNumber(prop1.value as number, prop2.value as number, amount, prop1.definition.lowValue, prop1.definition.highValue);
|
||||||
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (cont) continue;
|
|
||||||
|
|
||||||
for (let prop2 of end.props) {
|
// Interp vectors
|
||||||
if (!names.includes(prop2.definition.name)) continue;
|
if (typeof prop1.value === "object") {
|
||||||
if (typeof prop2.value !== "object") continue;
|
if (Object.keys(prop1.value).length !== 3) continue;
|
||||||
if (Object.keys(prop2.value).length !== 3) continue;
|
if (Object.keys(prop2.value).length !== 3) continue;
|
||||||
|
|
||||||
for (var i = 0; i < keys.length; i++) {
|
// Check x,y,z keys
|
||||||
if (!Object.keys(prop2.value).includes(keys[i])) {
|
var cont = false;
|
||||||
|
for (const key of vecKeys) {
|
||||||
|
if (
|
||||||
|
!Object.keys(prop1.value).includes(key) ||
|
||||||
|
!Object.keys(prop2.value).includes(key) ||
|
||||||
|
typeof prop1.value[key] !== "number" ||
|
||||||
|
typeof prop2.value[key] !== "number"
|
||||||
|
) {
|
||||||
cont = true;
|
cont = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cont) continue;
|
if (cont) continue;
|
||||||
|
|
||||||
if (prop1.definition.name !== prop2.definition.name) continue;
|
for (const key of vecKeys) {
|
||||||
if (prop1.definition.table !== prop2.definition.table) continue;
|
prop1.value[key] = interpNumber(prop1.value[key] as number, prop2.value[key] as number, amount, prop1.definition.lowValue, prop1.definition.highValue);
|
||||||
if (prop1.definition.ownerTableName !== prop2.definition.ownerTableName) continue;
|
}
|
||||||
if (prop1.definition.bitCount !== prop2.definition.bitCount) continue;
|
break;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,7 +147,7 @@ function incrementEntityTicks(entity: PacketEntity, amount: number): PacketEntit
|
||||||
];
|
];
|
||||||
for (let prop of entity.props) {
|
for (let prop of entity.props) {
|
||||||
if (names.includes(prop.definition.name)) {
|
if (names.includes(prop.definition.name)) {
|
||||||
prop.value = prop.value as number + amount;
|
prop.value = (prop.value as number) + amount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return entity;
|
return entity;
|
||||||
|
@ -129,10 +155,12 @@ function incrementEntityTicks(entity: PacketEntity, amount: number): PacketEntit
|
||||||
|
|
||||||
class InterpTransformer extends Parser {
|
class InterpTransformer extends Parser {
|
||||||
private readonly encoder: Encoder;
|
private readonly encoder: Encoder;
|
||||||
|
private readonly maxVel: number;
|
||||||
|
|
||||||
constructor(sourceStream: BitStream, targetStream: BitStream) {
|
constructor(sourceStream: BitStream, targetStream: BitStream, maxVel: number = 3500) {
|
||||||
super(sourceStream);
|
super(sourceStream);
|
||||||
this.encoder = new Encoder(targetStream);
|
this.encoder = new Encoder(targetStream);
|
||||||
|
this.maxVel = maxVel;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeMessage(message: Message) {
|
writeMessage(message: Message) {
|
||||||
|
@ -149,13 +177,14 @@ class InterpTransformer extends Parser {
|
||||||
public transform() {
|
public transform() {
|
||||||
const header = this.getHeader();
|
const header = this.getHeader();
|
||||||
header.frames *= 4;
|
header.frames *= 4;
|
||||||
//console.log(header);
|
|
||||||
this.encoder.encodeHeader(header);
|
this.encoder.encodeHeader(header);
|
||||||
|
|
||||||
var prevProgressPrint = 0;
|
var prevProgressPrint = 0;
|
||||||
var prevPacketMessage = null;
|
var prevPacketMessage = null;
|
||||||
var synced = false;
|
var synced = false;
|
||||||
var skippedFirst = false;
|
var skippedFirst = false;
|
||||||
|
var lastKnownProps = {};
|
||||||
|
var tickInterval = 0.015;
|
||||||
|
|
||||||
for (let message of this.iterateMessages()) {
|
for (let message of this.iterateMessages()) {
|
||||||
if (message.type === MessageType.SyncTick) {
|
if (message.type === MessageType.SyncTick) {
|
||||||
|
@ -179,36 +208,55 @@ class InterpTransformer extends Parser {
|
||||||
message.sequenceOut = prevPacketMessage.sequenceOut + 4;
|
message.sequenceOut = prevPacketMessage.sequenceOut + 4;
|
||||||
|
|
||||||
prevPacketMessage.packets = prevPacketMessage.packets.filter((value, index, arr) => {
|
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);
|
return ["netTick", "packetEntities"].includes(value.packetType);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Packets only contain props if they change during that tick,
|
||||||
|
// store last known props and restore previously stored ones.
|
||||||
|
for (let p of prevPacketMessage.packets) {
|
||||||
|
if (p.packetType !== "packetEntities") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let e of p.entities as PacketEntity[]) {
|
||||||
|
if (p.removedEntities.includes(e.entityIndex)) {
|
||||||
|
//console.log("packet includes removed ent");
|
||||||
|
delete (lastKnownProps[`${e.entityIndex}:${e.serverClass.id}`]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store from entity
|
||||||
|
for (const prop of e.props) {
|
||||||
|
if (!propNames.includes(prop.definition.name)) continue;
|
||||||
|
|
||||||
|
// Using just entityIndex will sometimes fail
|
||||||
|
// for some reason, maybe they are recycled.
|
||||||
|
// This happens even with the newPacket.removedEntities check
|
||||||
|
// later on...
|
||||||
|
if (lastKnownProps[`${e.entityIndex}:${e.serverClass.id}`] == null) {
|
||||||
|
lastKnownProps[`${e.entityIndex}:${e.serverClass.id}`] = {};
|
||||||
|
}
|
||||||
|
lastKnownProps[`${e.entityIndex}:${e.serverClass.id}`][prop.definition.fullName] = prop;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore props later on after checking what props the future entities have
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add 3 intepolated ticks
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
prevPacketMessage.tick++;
|
prevPacketMessage.tick++;
|
||||||
prevPacketMessage.sequenceIn++;
|
prevPacketMessage.sequenceIn++;
|
||||||
prevPacketMessage.sequenceOut++;
|
prevPacketMessage.sequenceOut++;
|
||||||
|
|
||||||
for (let packet of prevPacketMessage.packets) {
|
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") {
|
if (packet.packetType === "netTick") {
|
||||||
packet.tick++;
|
packet.tick++;
|
||||||
|
tickInterval = packet.frameTime / 100000;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet.packetType === "packetEntities") {
|
// packetEntities
|
||||||
for (let entity of packet.entities) {
|
for (let entity of packet.entities as PacketEntity[]) {
|
||||||
// if (entity.entityIndex > 64) {
|
|
||||||
// // max players
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (entity.serverClass.name !== "CTFPlayer") {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Loop through new message and interp
|
// Loop through new message and interp
|
||||||
for (let newPacket of message.packets) {
|
for (let newPacket of message.packets) {
|
||||||
|
@ -216,40 +264,114 @@ class InterpTransformer extends Parser {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const index of newPacket.removedEntities) {
|
||||||
|
for (let propIndex = Object.keys(lastKnownProps).length - 1; propIndex > 0; propIndex--) {
|
||||||
|
if (Object.keys(lastKnownProps)[propIndex].startsWith(index.toString() + ":")) {
|
||||||
|
delete (lastKnownProps[Object.keys(lastKnownProps)[propIndex]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPacket.removedEntities.includes(entity.entityIndex)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (let newEntity of newPacket.entities) {
|
for (let newEntity of newPacket.entities) {
|
||||||
// if (newEntity.entityIndex > 64) {
|
|
||||||
// // max players
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (newEntity.serverClass.name !== "CTFPlayer") {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (newEntity.entityIndex !== entity.entityIndex) {
|
if (newEntity.entityIndex !== entity.entityIndex) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We interpolate same entity multiple times,
|
if (newEntity.serverClass.id !== entity.serverClass.id) {
|
||||||
// edit interp amount appropriately.
|
continue;
|
||||||
// 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);
|
entity = incrementEntityTicks(entity, 1);
|
||||||
newEntity = incrementEntityTicks(newEntity, 1);
|
newEntity = incrementEntityTicks(newEntity, 1);
|
||||||
|
|
||||||
|
// Don't interp if new entity has EF_NOINTERP flag
|
||||||
|
var m_fEffects = newEntity.getPropValue("DT_BaseEntity.m_fEffects");
|
||||||
|
if (m_fEffects !== null) {
|
||||||
|
if (((m_fEffects as number) & EF_NOINTERP) === EF_NOINTERP) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for maxVel
|
||||||
|
if (lastKnownProps[`${entity.entityIndex}:${entity.serverClass.id}`] != null) {
|
||||||
|
var lastOrigin = null;
|
||||||
|
var lastOriginZ = null;
|
||||||
|
var newOrigin = null;
|
||||||
|
var newOriginZ = null;
|
||||||
|
|
||||||
|
for (const p of newEntity.props) {
|
||||||
|
if (p.definition.name === "m_vecOrigin") {
|
||||||
|
newOrigin = p.value;
|
||||||
|
if (lastKnownProps[`${entity.entityIndex}:${entity.serverClass.id}`][p.definition.fullName] != null) {
|
||||||
|
lastOrigin = lastKnownProps[`${entity.entityIndex}:${entity.serverClass.id}`][p.definition.fullName].value;
|
||||||
|
}
|
||||||
|
} else if (p.definition.name === "m_vecOrigin[2]") {
|
||||||
|
newOriginZ = p.value;
|
||||||
|
if (lastKnownProps[`${entity.entityIndex}:${entity.serverClass.id}`][p.definition.fullName] != null) {
|
||||||
|
lastOriginZ = lastKnownProps[`${entity.entityIndex}:${entity.serverClass.id}`][p.definition.fullName].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastOrigin != null && newOrigin != null) {
|
||||||
|
if (lastOriginZ) {
|
||||||
|
lastOrigin.z = lastOriginZ;
|
||||||
|
}
|
||||||
|
if (newOriginZ) {
|
||||||
|
newOrigin.z = newOriginZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let axis of vecKeys) {
|
||||||
|
var dist = newOrigin[axis] - lastOrigin[axis];
|
||||||
|
if (dist > this.maxVel * tickInterval * 4) {
|
||||||
|
//console.log(`Entity ${newEntity.serverClass.name} (${newEntity.entityIndex}) moved over maxvel (${dist} > ${this.maxVel * tickInterval * 4})`);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore from previously stored props only the props
|
||||||
|
// that the future entity has, so we can interp between them.
|
||||||
|
// Only need to do this the first time,
|
||||||
|
// since we reuse the message.
|
||||||
|
if (i < 0) {
|
||||||
|
if (lastKnownProps[`${entity.entityIndex}:${entity.serverClass.id}`] != null) {
|
||||||
|
var propArr = [];
|
||||||
|
|
||||||
|
for (const propKey of Object.keys(lastKnownProps[`${entity.entityIndex}:${entity.serverClass.id}`])) {
|
||||||
|
// Check if new entity has this property,
|
||||||
|
// if not, no need to add it to the old entity for interpolation.
|
||||||
|
if (newEntity.hasProperty(propKey.split(".")[0], propKey.split(".")[1])) {
|
||||||
|
propArr.push(lastKnownProps[`${entity.entityIndex}:${entity.serverClass.id}`][propKey]);
|
||||||
}
|
}
|
||||||
//console.log(`interp tick: ${prevPacketMessage.tick}, in: ${prevPacketMessage.sequenceIn}, out: ${prevPacketMessage.sequenceOut}`);
|
}
|
||||||
|
|
||||||
|
if (propArr.length > 0) {
|
||||||
|
//console.log(`Applying ${propArr.length} props`);
|
||||||
|
entity.applyPropUpdate(propArr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We interpolate same entity multiple times,
|
||||||
|
// edit interp amount appropriately.
|
||||||
|
// min target max interp
|
||||||
|
// 0 0.25 1.0 0.25
|
||||||
|
// 0.25 0.5 1.0 1/3
|
||||||
|
// 0.5 0.75 1.0 0.5
|
||||||
|
var interp = 0.25 * (1 / (1 - (0.25 * i)));
|
||||||
|
|
||||||
|
entity = interpEntity(entity, newEntity, interp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.writeMessage(prevPacketMessage);
|
this.writeMessage(prevPacketMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,10 +389,6 @@ class InterpTransformer extends Parser {
|
||||||
skippedFirst = true;
|
skippedFirst = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (message.type === MessageType.Packet) {
|
|
||||||
// console.log(`tick: ${message.tick}, in: ${message.sequenceIn}, out: ${message.sequenceOut}`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Write current message
|
// Write current message
|
||||||
this.writeMessage(message);
|
this.writeMessage(message);
|
||||||
}
|
}
|
||||||
|
@ -282,11 +400,11 @@ class InterpTransformer extends Parser {
|
||||||
* @param input - Input demofile name
|
* @param input - Input demofile name
|
||||||
* @param output - Output demofile name
|
* @param output - Output demofile name
|
||||||
*/
|
*/
|
||||||
function interp(input: string, output: string) {
|
function interp(input: string, output: string, maxVel: number = 3500) {
|
||||||
const decodeStream = new BitStream(readFileSync(input).buffer as ArrayBuffer);
|
const decodeStream = new BitStream(readFileSync(input).buffer as ArrayBuffer);
|
||||||
const encodeStream = new DynamicBitStream(32 * 1024 * 1024);
|
const encodeStream = new DynamicBitStream(32 * 1024 * 1024);
|
||||||
|
|
||||||
const transformer = new InterpTransformer(decodeStream, encodeStream);
|
const transformer = new InterpTransformer(decodeStream, encodeStream, maxVel);
|
||||||
transformer.transform();
|
transformer.transform();
|
||||||
|
|
||||||
const encodedLength = encodeStream.index;
|
const encodedLength = encodeStream.index;
|
||||||
|
|
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue