player count fix wip

This commit is contained in:
Robin Appelman 2025-10-08 23:23:34 +02:00
commit 04790ad6f4
9 changed files with 403 additions and 335 deletions

1
.gitignore vendored
View file

@ -9,3 +9,4 @@ out.dem
result result
dist dist
node_modules node_modules
*.txt

595
wasm/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -17,9 +17,9 @@ default = ["console_error_panic_hook"]
cli = ["clap", "env_logger", "typed-builder"] cli = ["clap", "env_logger", "typed-builder"]
[dependencies] [dependencies]
bitbuffer = "0.11.1" bitbuffer = "0.11.3"
tf-demo-parser = { version = "0.6" } #tf-demo-parser = { version = "0.6" }
#tf-demo-parser = { version = "0.4", path = "../tf-demo-parser" } tf-demo-parser = { version = "0.7", path = "../../tf-demo-parser", features = ["write"] }
wasm-bindgen = { version = "0.2.45" } wasm-bindgen = { version = "0.2.45" }
serde-wasm-bindgen = "0.6.5" serde-wasm-bindgen = "0.6.5"
web-sys = { version = "0.3", features = ["console"] } web-sys = { version = "0.3", features = ["console"] }

View file

@ -127,7 +127,7 @@ impl ActiveEntities {
for entity in self.entities.values_mut() { for entity in self.entities.values_mut() {
match state.instance_baselines[0].get(entity.entity_index) { match state.instance_baselines[0].get(entity.entity_index) {
Some(baseline_entity) if baseline_entity.server_class == entity.server_class => { Some(baseline_entity) if baseline_entity.server_class == entity.server_class => {
entity.update_type = UpdateType::Preserve; entity.update_type = UpdateType::Delta;
} }
Some(_baseline_entity) => { Some(_baseline_entity) => {
// encode the baseline if the baseline server class differs // encode the baseline if the baseline server class differs

View file

@ -15,13 +15,16 @@ struct Args {
to: Option<u32>, to: Option<u32>,
#[arg(long)] #[arg(long)]
remove_invalid_deletes: bool, remove_invalid_deletes: bool,
#[arg(long)]
fix_player_count: bool,
} }
impl Args { impl Args {
fn get_options(&self) -> EditOptions { fn get_options(&self) -> EditOptions {
let builder = EditOptions::builder() let builder = EditOptions::builder()
.unlock_pov(self.unlock_pov) .unlock_pov(self.unlock_pov)
.remove_invalid_deletes(self.remove_invalid_deletes); .remove_invalid_deletes(self.remove_invalid_deletes)
.fix_player_count(self.fix_player_count);
if let (Some(from), Some(to)) = (self.from, self.to) { if let (Some(from), Some(to)) = (self.from, self.to) {
builder.cut(TickRange { builder.cut(TickRange {
from: from.into(), from: from.into(),

View file

@ -5,6 +5,7 @@ pub mod missing_preserve;
mod mutate; mod mutate;
mod options; mod options;
mod pov; mod pov;
mod player_count;
use bitbuffer::{BitRead, BitWriteStream, LittleEndian}; use bitbuffer::{BitRead, BitWriteStream, LittleEndian};
use tf_demo_parser::demo::header::Header; use tf_demo_parser::demo::header::Header;
@ -88,19 +89,23 @@ fn no_cut(input: &[u8], options: EditOptions) -> Vec<u8> {
let mut handler = DemoHandler::default(); let mut handler = DemoHandler::default();
handler.handle_header(&header); handler.handle_header(&header);
let mut output_handler = DemoHandler::default();
output_handler.handle_header(&header);
let mutators = options.as_mutator(spectator_id); let mutators = options.as_mutator(spectator_id);
while let Some(mut packet) = packets.next(&handler.state_handler).unwrap() { while let Some(mut packet) = packets.next(&handler.state_handler).unwrap() {
handler.handle_packet(packet.clone()).expect("failed to handle source packet");
mutators.mutate_packet(&mut packet, &handler.state_handler); mutators.mutate_packet(&mut packet, &handler.state_handler);
output_handler.handle_packet(packet.clone()).expect("failed to handle mutated packet");
if packet.packet_type() != PacketType::ConsoleCmd if packet.packet_type() != PacketType::ConsoleCmd
&& packet.packet_type() != PacketType::UserCmd && packet.packet_type() != PacketType::UserCmd
{ {
packet packet
.encode(&mut out_stream, &handler.state_handler) .encode(&mut out_stream, &output_handler.state_handler)
.unwrap(); .unwrap();
} }
handler.handle_packet(packet).unwrap();
} }
} }
out_buffer out_buffer

View file

@ -27,7 +27,7 @@ impl MessageMutator for RemoveInvalidPreserveEntity {
UpdateType::Enter => { UpdateType::Enter => {
self.known_entities.borrow_mut().insert(entity.entity_index); self.known_entities.borrow_mut().insert(entity.entity_index);
} }
UpdateType::Preserve => { UpdateType::Delta => {
if !self.known_entities.borrow().contains(&entity.entity_index) { if !self.known_entities.borrow().contains(&entity.entity_index) {
warn!("preserving missing entity {}", entity.entity_index); warn!("preserving missing entity {}", entity.entity_index);
} }

View file

@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
use tf_demo_parser::demo::data::DemoTick; use tf_demo_parser::demo::data::DemoTick;
use tf_demo_parser::demo::message::packetentities::EntityId; use tf_demo_parser::demo::message::packetentities::EntityId;
use tf_demo_parser::demo::message::Message; use tf_demo_parser::demo::message::Message;
use crate::player_count::fix_player_count;
#[derive(Debug, Serialize, Deserialize, Default)] #[derive(Debug, Serialize, Deserialize, Default)]
#[non_exhaustive] #[non_exhaustive]
@ -20,6 +21,9 @@ pub struct EditOptions {
#[serde(default)] #[serde(default)]
#[cfg_attr(feature = "cli", builder(default))] #[cfg_attr(feature = "cli", builder(default))]
pub remove_invalid_deletes: bool, pub remove_invalid_deletes: bool,
#[serde(default)]
#[cfg_attr(feature = "cli", builder(default))]
pub fix_player_count: bool,
} }
impl EditOptions { impl EditOptions {
@ -51,6 +55,10 @@ impl EditOptions {
removed_invalid_deletes(&mut mutators); removed_invalid_deletes(&mut mutators);
} }
if self.fix_player_count {
fix_player_count(&mut mutators);
}
mutators mutators
} }
} }

120
wasm/src/player_count.rs Normal file
View file

@ -0,0 +1,120 @@
use crate::mutate::{MutatorList, PacketMutator};
use tf_demo_parser::demo::packet::Packet;
use tf_demo_parser::ParserState;
#[derive(Default)]
pub struct PlayerCount {}
/// property arrays that got renamed and new items
const PLAYER_ARRAY_PROPS_RENAMED: &[(&str, u16, u16)] = &[
("m_aBonusPlayerRoll_33", 33, 101),
("m_ConditionData_128", 128, 131),
];
/// property arrays that just got new items
const PLAYER_ARRAY_PROPS: &[(&str, u16)] = &[
("m_iPing", 101),
("m_iScore", 101),
("m_iDeaths", 101),
("m_bConnected", 101),
("m_iTeam", 101),
("m_bAlive", 101),
("m_iHealth", 101),
("m_iAccountID", 101),
("m_bValid", 101),
("m_iUserID", 101),
("m_bPlayerReady", 101),
("m_ePlayerWantsRematch", 101),
("m_bPlayerIsPackMember", 101),
("m_bPlayerDominated", 101),
("m_bPlayerDominatingMe", 101),
("m_iTotalScore", 101),
("m_iMaxHealth", 101),
("m_iMaxBuffedHealth", 101),
("m_iPlayerClass", 101),
("m_bArenaSpectator", 101),
("m_iActiveDominations", 101),
("m_flNextRespawnTime", 101),
("m_iChargeLevel", 101),
("m_iDamage", 101),
("m_iDamageAssist", 101),
("m_iDamageBoss", 101),
("m_iHealing", 101),
("m_iHealingAssist", 101),
("m_iDamageBlocked", 101),
("m_iCurrencyCollected", 101),
("m_iBonusPoints", 101),
("m_iPlayerLevel", 101),
("m_iUpgradeRefundCredits", 101),
("m_iBuybackCredits", 101),
("m_iPlayerClassWhenKilled", 101),
("m_iConnectionState", 101),
("m_flConnectTime", 101),
("m_iStreaks", 407),
];
// property values that got changed
const PLAYER_PROPS_VALUES: &[(&str, &str, u16, u16)] = &[
("DT_ObjectDispenser", r#""healing_array""#, 33, 101),
("DT_Team", r#""player_array""#, 33, 101),
("DT_TFTeam", r#""team_object_array""#, 198, 606),
];
impl PacketMutator for PlayerCount {
fn mutate_packet(&self, packet: &mut Packet, _state: &ParserState) {
if let Packet::DataTables(data_tables) = packet {
for table in &mut data_tables.tables {
let table_name = table.name.as_str();
if let Some(new_length) = PLAYER_ARRAY_PROPS.iter().copied().find_map(|(name, new_length)| (name == table_name).then_some(new_length)) {
let base = table.props.last().unwrap().clone();
let old_length = table.props.len() as u16;
for i in old_length ..= new_length {
let mut prop = base.clone();
prop.name = format!("{i:03}").into();
table.props.push(prop);
}
} else if let Some((old_length, new_length)) = PLAYER_ARRAY_PROPS_RENAMED.iter().copied().find_map(|(name, old_length, new_length)| table_name.ends_with(name).then_some((old_length, new_length))) {
if table_name.starts_with("_ST_") {
let base = table.props.last().unwrap().clone();
for i in old_length.. new_length { // non-inclusive range, because for some reasons these arrays are one short
let mut prop = base.clone();
prop.name = format!("{i:03}").into();
table.props.push(prop);
}
} else if table_name.starts_with("_LPT_") {
table.props[0].name = format!("lengthprop{new_length}").into();
}
let name_prefix = table_name.rsplit_once('_').unwrap().0;
table.name = format!("{name_prefix}_{}", new_length).into();
} else if let Some((prop_name, _, new_length)) = PLAYER_PROPS_VALUES.iter().copied().find_map(|(name, prop_name, old_length, new_length)| table_name.eq(name).then_some((prop_name, old_length, new_length))) {
for prop in &mut table.props {
if prop.name == prop_name {
prop.element_count = new_length.into();
}
}
}
for prop in &mut table.props {
if let Some(target_name) = &mut prop.table_name {
if let Some((_, new_length)) = PLAYER_ARRAY_PROPS_RENAMED.iter().copied().find_map(|(name, old_length, new_length)| target_name.ends_with(name).then_some((old_length, new_length))) {
let name_prefix = target_name.rsplit_once('_').unwrap().0;
*target_name = format!("{name_prefix}_{}", new_length).into();
}
}
}
// let table_name = table.name.as_str();
// for prop in &mut table.props {
// println!(
// "{}.{}: {}",
// table_name,
// prop.name,
// prop.element_count.unwrap_or_default()
// );
// }
}
}
}
}
pub fn fix_player_count(mutators: &mut MutatorList) {
mutators.push_packet_mutator(PlayerCount::default())
}