demo-interp/build/index.js

360 lines
18 KiB
JavaScript
Raw Normal View History

2020-01-16 22:01:11 +02:00
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.interp = void 0;
2020-01-16 22:01:11 +02:00
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 interpNumber(start, end, amount, lowValue, highValue) {
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",
// DT_BaseEntity
"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[1]",
// DT_LocalPlayerExclusive
"m_vecViewOffset[0]",
"m_vecViewOffset[1]",
"m_vecViewOffset[2]",
"m_vecBaseVelocity",
"m_vecVelocity[0]",
"m_vecVelocity[1]",
"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_vecMaxs",
"m_vecMinsPreScaled",
"m_vecMaxsPreScaled",
"m_vecSpecifiedSurroundingMins",
"m_vecSpecifiedSurroundingMaxs",
"m_vecSpecifiedSurroundingMinsPreScaled",
"m_vecSpecifiedSurroundingMaxsPreScaled",
];
const vecKeys = ["x", "y", "z"];
const EF_NOINTERP = 8;
2020-01-16 22:01:11 +02:00
function interpEntity(start, end, amount) {
for (let prop1 of start.props) {
if (!propNames.includes(prop1.definition.name))
2020-01-16 22:01:11 +02:00
continue;
for (let prop2 of end.props) {
if (!propNames.includes(prop2.definition.name))
2020-01-16 22:01:11 +02:00
continue;
if (prop1.definition.fullName !== prop2.definition.fullName)
2020-01-16 22:01:11 +02:00
continue;
if (typeof prop1.value === "number") {
prop1.value = interpNumber(prop1.value, prop2.value, amount, prop1.definition.lowValue, prop1.definition.highValue);
2020-01-16 22:01:11 +02:00
break;
}
// Interp vectors
if (typeof prop1.value === "object") {
if (Object.keys(prop1.value).length !== 3)
continue;
if (Object.keys(prop2.value).length !== 3)
continue;
// Check x,y,z keys
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;
break;
}
2020-01-16 22:01:11 +02:00
}
if (cont)
continue;
for (const key of vecKeys) {
prop1.value[key] = interpNumber(prop1.value[key], prop2.value[key], amount, prop1.definition.lowValue, prop1.definition.highValue);
}
break;
2020-01-16 22:01:11 +02:00
}
}
}
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, maxVel = 3500) {
2020-01-16 22:01:11 +02:00
super(sourceStream);
this.encoder = new Encoder_1.Encoder(targetStream);
this.maxVel = maxVel;
2020-01-16 22:01:11 +02:00
}
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;
this.encoder.encodeHeader(header);
var prevProgressPrint = 0;
var prevPacketMessage = null;
var synced = false;
var skippedFirst = false;
var lastKnownProps = {};
var tickInterval = 0.015;
2020-01-16 22:01:11 +02:00
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) => {
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
2020-01-16 22:01:11 +02:00
for (let i = 0; i < 3; i++) {
prevPacketMessage.tick++;
prevPacketMessage.sequenceIn++;
prevPacketMessage.sequenceOut++;
for (let packet of prevPacketMessage.packets) {
if (packet.packetType === "netTick") {
packet.tick++;
tickInterval = packet.frameTime / 100000;
2020-01-16 22:01:11 +02:00
continue;
}
// packetEntities
for (let entity of packet.entities) {
// Loop through new message and interp
for (let newPacket of message.packets) {
if (newPacket.packetType !== "packetEntities") {
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) {
if (newEntity.entityIndex !== entity.entityIndex) {
2020-01-16 22:01:11 +02:00
continue;
}
if (newEntity.serverClass.id !== entity.serverClass.id) {
continue;
}
entity = incrementEntityTicks(entity, 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);
}
2020-01-16 22:01:11 +02:00
}
}
// 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);
2020-01-16 22:01:11 +02:00
}
}
}
}
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;
}
// 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, maxVel = 3500) {
2020-01-16 22:01:11 +02:00
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, maxVel);
2020-01-16 22:01:11 +02:00
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