"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.interp = void 0; 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; function interpEntity(start, end, amount) { for (let prop1 of start.props) { if (!propNames.includes(prop1.definition.name)) continue; for (let prop2 of end.props) { if (!propNames.includes(prop2.definition.name)) continue; if (prop1.definition.fullName !== prop2.definition.fullName) continue; if (typeof prop1.value === "number") { prop1.value = interpNumber(prop1.value, prop2.value, amount, prop1.definition.lowValue, prop1.definition.highValue); 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; } } 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; } } } 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) { super(sourceStream); this.encoder = new Encoder_1.Encoder(targetStream); this.maxVel = maxVel; } 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; for (let message of this.iterateMessages()) { if (message.type === Message_1.MessageType.SyncTick) { //
// // // // // // // | tick 4, the fat network packet - probably contains initial states of everything // | tick 8, delta packets start here?; i think we only want to manipulate these ones // ... // 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 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; 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) { 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); } } } // 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); } } 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) { 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); 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