mirror of
https://codeberg.org/demostf/parser.git
synced 2026-06-03 10:14:06 +02:00
merge codegen into main crate
This commit is contained in:
parent
c08f30a60c
commit
1a549b7c40
14 changed files with 261 additions and 890 deletions
30
src/bin/codegen.rs
Normal file
30
src/bin/codegen.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use main_error::MainError;
|
||||
use prettyplease::unparse;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use syn::{parse2, File};
|
||||
use tf_demo_parser::codegen::gameevent::generate_game_events;
|
||||
use tf_demo_parser::codegen::propnames::generate_prop_names;
|
||||
use tf_demo_parser::Demo;
|
||||
|
||||
fn main() -> std::result::Result<(), MainError> {
|
||||
better_panic::install();
|
||||
|
||||
let args: Vec<_> = env::args().collect();
|
||||
if args.len() < 2 {
|
||||
println!("1 argument required");
|
||||
return Ok(());
|
||||
}
|
||||
let path = args[1].clone();
|
||||
let file = fs::read(path).expect("Unable to read file");
|
||||
let demo = Demo::new(&file);
|
||||
let tokens = match args.get(2).map(|s| s.as_str()) {
|
||||
None | Some("events") => generate_game_events(demo),
|
||||
Some("props") => generate_prop_names(demo),
|
||||
_ => panic!("unsupported"),
|
||||
};
|
||||
let file = parse2::<File>(tokens)?;
|
||||
let code = unparse(&file);
|
||||
println!("{}", code);
|
||||
Ok(())
|
||||
}
|
||||
469
src/codegen/gameevent.rs
Normal file
469
src/codegen/gameevent.rs
Normal file
|
|
@ -0,0 +1,469 @@
|
|||
extern crate proc_macro;
|
||||
|
||||
use crate::demo::gameevent_gen::get_sizes;
|
||||
use crate::demo::gamevent::{GameEventDefinition, GameEventValueType};
|
||||
use crate::demo::parser::MessageHandler;
|
||||
use crate::{Demo, ParserState};
|
||||
use crate::{DemoParser, MessageType};
|
||||
use fnv::FnvHashMap;
|
||||
use inflector::Inflector;
|
||||
use lazy_static::lazy_static;
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::quote;
|
||||
|
||||
struct GameEventAnalyser;
|
||||
|
||||
impl MessageHandler for GameEventAnalyser {
|
||||
type Output = Vec<GameEventDefinition>;
|
||||
|
||||
fn does_handle(_message_type: MessageType) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn into_output(self, state: &ParserState) -> Self::Output {
|
||||
state.event_definitions.clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn should_box_event(name: &str) -> bool {
|
||||
lazy_static! {
|
||||
static ref SIZES: FnvHashMap<&'static str, usize> = get_sizes();
|
||||
}
|
||||
|
||||
SIZES.get(name).cloned().unwrap_or_default() > 120
|
||||
}
|
||||
|
||||
fn get_type_name(ty: GameEventValueType) -> &'static str {
|
||||
match ty {
|
||||
GameEventValueType::String => "MaybeUtf8String",
|
||||
GameEventValueType::Float => "f32",
|
||||
GameEventValueType::Boolean => "bool",
|
||||
GameEventValueType::Byte => "u8",
|
||||
GameEventValueType::Local => "()",
|
||||
GameEventValueType::Long => "u32",
|
||||
GameEventValueType::Short => "u16",
|
||||
GameEventValueType::None => "()",
|
||||
}
|
||||
}
|
||||
|
||||
fn get_entry_name(name: &str) -> String {
|
||||
lazy_static! {
|
||||
static ref REPLACEMENTS: Vec<(&'static str, &'static str)> = vec!(
|
||||
("mapname", "map_name"),
|
||||
("cvarname", "cvar_name"),
|
||||
("cvarvalue", "cvar_value"),
|
||||
("userid", "user_id"),
|
||||
("networkid", "network_id"),
|
||||
("teamid", "team_id"),
|
||||
("teamname", "team_name"),
|
||||
("oldteam", "old_team"),
|
||||
("autoteam", "auto_team"),
|
||||
("entindex", "ent_index"),
|
||||
("weaponid", "weapon_id"),
|
||||
("damagebit", "damage_bit"),
|
||||
("customkill", "custom_kill"),
|
||||
("logclassname", "log_class_name"),
|
||||
("playerpenetratecount", "player_penetrate_count"),
|
||||
("damageamount", "damage_amount"),
|
||||
("showdisguisedcrit", "show_disguised_crit"),
|
||||
("minicrit", "mini_crit"),
|
||||
("allseecrit", "all_see_crit"),
|
||||
("weaponid", "weapon_id"),
|
||||
("bonuseffect", "bonus_effect"),
|
||||
("teamonly", "team_only"),
|
||||
("oldname", "old_name"),
|
||||
("newname", "new_name"),
|
||||
("hintmessage", "hint_message"),
|
||||
("roundslimit", "rounds_limit"),
|
||||
("timelimit", "time_limit"),
|
||||
("fraglimit", "frag_limit"),
|
||||
("numadvanced", "num_advanced"),
|
||||
("numbronze", "num_bronze"),
|
||||
("numsilver", "num_silver"),
|
||||
("numgold", "num_gold"),
|
||||
("oldmode", "old_mode"),
|
||||
("newmode", "new_mode"),
|
||||
("entityid", "entity_id"),
|
||||
("winreason", "win_reason"),
|
||||
("flagcaplimit", "flag_cap_limit"),
|
||||
("cpname", "cp_name"),
|
||||
("capteam", "cap_team"),
|
||||
("captime", "cap_time"),
|
||||
("eventtype", "event_type"),
|
||||
("killstreak", "kill_stream"),
|
||||
("forceupload", "force_upload"),
|
||||
("targetid", "target_id"),
|
||||
("isbuilder", "is_builder"),
|
||||
("objecttype", "object_type"),
|
||||
("namechange", "name_change"),
|
||||
("readystate", "ready_state"),
|
||||
("builderid", "builder_id"),
|
||||
("recedetime", "recede_time"),
|
||||
("ownerid", "owner_id"),
|
||||
("sapperid", "sapper_id"),
|
||||
("itemdef", "item_def"),
|
||||
("bitfield", "bit_field"),
|
||||
("playsound", "play_sound"),
|
||||
("totalhits", "total_hits"),
|
||||
("posx", "pos_x"),
|
||||
("posy", "pos_y"),
|
||||
("posz", "pos_z"),
|
||||
("ineye", "in_eye"),
|
||||
("maxplayers", "max_players"),
|
||||
("levelname", "level_name"),
|
||||
("isstrange", "is_strange"),
|
||||
("isunusual", "is_unusual"),
|
||||
("defindex", "definition_index"),
|
||||
("matchgroup", "match_group")
|
||||
);
|
||||
}
|
||||
|
||||
if name == "type" {
|
||||
return "kind".to_string();
|
||||
}
|
||||
|
||||
let mut snake: String = name.to_snake_case();
|
||||
for (search, replace) in REPLACEMENTS.iter() {
|
||||
snake = snake.replace(search, replace);
|
||||
}
|
||||
|
||||
snake
|
||||
}
|
||||
|
||||
fn get_event_name(name: &str) -> String {
|
||||
lazy_static! {
|
||||
static ref REPLACEMENTS: Vec<(&'static str, &'static str)> = vec!(
|
||||
("ReplayReplaysavailable", "ReplayReplaysAvailable"),
|
||||
("ServerAddban", "ServerAddBan"),
|
||||
("ServerRemoveban", "ServerRemoveBan"),
|
||||
("ClientBeginconnect", "ClientBeginConnect"),
|
||||
("ClientFullconnect", "ClientFullConnect"),
|
||||
("PlayerChangename", "PlayerChangeName"),
|
||||
("PlayerHintmessage", "PlayerHintMessage"),
|
||||
("GameNewmap", "GameNewMap"),
|
||||
("IntroNextcamera", "IntroNextCamera"),
|
||||
("PlayerChangeclass", "PlayerChangeClass"),
|
||||
("Updateimages", "UpdateImages"),
|
||||
("Updatelayout", "UpdateLayout"),
|
||||
("Updatecapping", "UpdateCapping"),
|
||||
("Updateowner", "UpdateOwner"),
|
||||
("Starttouch", "StartTouch"),
|
||||
("Endtouch", "EndTouch"),
|
||||
("FakeCaptureMult", "FakeCaptureMultiplier"),
|
||||
("TeamplayWaitingAbouttoend", "TeamPlayWaitingAboutToEnd"),
|
||||
("TeamplayPointStartcapture", "TeamPlayPointStartCapture"),
|
||||
("FreezecamStarted", "FreezeCamStarted"),
|
||||
("LocalplayerChangeteam", "LocalPlayerChangeTeam"),
|
||||
("LocalplayerChangeclass", "LocalPlayerChangeClass"),
|
||||
("LocalplayerChangedisguise", "LocalPlayerChangeDisguise"),
|
||||
("FlagstatusUpdate", "FlagStatusUpdate"),
|
||||
("TournamentEnablecountdown", "TournamentEnableCountdown"),
|
||||
("PlayerCalledformedic", "PlayerCalledForMedic"),
|
||||
("PlayerAskedforball", "PlayerAskedForBall"),
|
||||
("LocalplayerBecameobserver", "LocalPlayerBecameObserver"),
|
||||
("PlayerHealedmediccall", "PlayerHealedMedicCall"),
|
||||
("ArenaMatchMaxstreak", "ArenaMatchMaxStreak"),
|
||||
("StatsResetround", "StatsResetRound"),
|
||||
("FishNotice_arm", "FishNoticeArm"),
|
||||
("PlayerBonuspoints", "PlayerBonusPoints"),
|
||||
("PlayerUsedPowerupBottle", "PlayerUsedPowerUpBottle"),
|
||||
("ReplayStartrecord", "ReplayStartRecord"),
|
||||
("ReplaySessioninfo", "ReplaySessionInfo"),
|
||||
("ReplayEndrecord", "ReplayEndRecord"),
|
||||
("ReplayServererror", "ReplayServerError"),
|
||||
("Teamplay", "TeamPlay"),
|
||||
("death", "Death"),
|
||||
("panel", "Panel"),
|
||||
("object", "Object"),
|
||||
("update", "Update"),
|
||||
("ready", "Ready"),
|
||||
("Gameui", "GameUI"),
|
||||
("onhit", "OnHit"),
|
||||
("bymedic", "ByMedic"),
|
||||
("Controlpoint", "ControlPoint"),
|
||||
("Pipebomb", "PipeBomb"),
|
||||
("Scorestats", "ScoreStats"),
|
||||
("Creditbonus", "CreditBonus"),
|
||||
("Sentrybuster", "SentryBuster"),
|
||||
("Questlog", "QuestLog"),
|
||||
("Localplayer", "LocalPlayer"),
|
||||
("Minigame", "MiniGame"),
|
||||
("Winlimit", "WinLimit"),
|
||||
("Skillrating", "SkillRating"),
|
||||
("Directhit", "DirectHit"),
|
||||
("Chargedeployed", "ChargeDeployed"),
|
||||
("Winddown", "WindDown"),
|
||||
("Stealsandvich", "StealSandvich"),
|
||||
("Pricesheet", "PriceSheet"),
|
||||
("Teambalanced", "TeamBalanced"),
|
||||
("Highfive", "HighFive"),
|
||||
("Powerup", "PowerUp"),
|
||||
("Hltv", "HLTV"),
|
||||
("Changelevel", "ChangeLevel"),
|
||||
("Rocketpack", "RocketPack"),
|
||||
("Deadringer", "DeadRinger"),
|
||||
("Mainmenu", "MainMenu"),
|
||||
("Mmstats", "MMStats"),
|
||||
);
|
||||
}
|
||||
|
||||
let mut snake: String = name.to_pascal_case();
|
||||
for (search, replace) in REPLACEMENTS.iter() {
|
||||
snake = snake.replace(search, replace);
|
||||
}
|
||||
|
||||
snake
|
||||
}
|
||||
|
||||
pub fn generate_game_events(demo: Demo) -> TokenStream {
|
||||
let (_, mut events) = DemoParser::new_with_analyser(demo.get_stream(), GameEventAnalyser)
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
events.sort();
|
||||
|
||||
let span = Span::call_site();
|
||||
|
||||
let imports = quote!(
|
||||
use super::gamevent::{EventValue, GameEventDefinition, GameEventEntry, RawGameEvent};
|
||||
use crate::demo::Stream;
|
||||
use crate::{ParseError, Result};
|
||||
use bitbuffer::{BitRead, LittleEndian, BitWrite, BitWriteStream};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use crate::demo::data::MaybeUtf8String;
|
||||
);
|
||||
|
||||
let event_definitions = events.iter().map(|event| {
|
||||
let fields = event.entries.iter().map(|entry| {
|
||||
let name = Ident::new(&get_entry_name(&entry.name), span);
|
||||
let ty = Ident::new(get_type_name(entry.kind), span);
|
||||
|
||||
quote!(pub #name: #ty,)
|
||||
});
|
||||
|
||||
let name = Ident::new(
|
||||
&format!("{}Event", get_event_name(event.event_type.as_str())),
|
||||
span,
|
||||
);
|
||||
|
||||
let entry_readers = event.entries.iter().map(|entry| {
|
||||
let name_str = get_entry_name(&entry.name);
|
||||
let name = Ident::new(&name_str, span);
|
||||
let ty = Ident::new(get_type_name(entry.kind), span);
|
||||
|
||||
quote!(
|
||||
#name: read_value::<#ty>(stream, iter.next(), #name_str)?,
|
||||
)
|
||||
});
|
||||
|
||||
let definition_iter = if event.entries.len() > 0 {
|
||||
quote!(
|
||||
let mut iter = definition.entries.iter();
|
||||
)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
quote!(
|
||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||
#[derive(Debug, BitWrite, PartialEq, Serialize, Deserialize, Clone)]
|
||||
pub struct #name {
|
||||
#(#fields)*
|
||||
}
|
||||
|
||||
impl #name {
|
||||
#[allow(unused_variables)]
|
||||
fn read(stream: &mut Stream, definition: &GameEventDefinition) -> Result<Self> {
|
||||
#definition_iter
|
||||
|
||||
Ok(#name {
|
||||
#(#entry_readers)*
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
let event_variants = events.iter().map(|event| {
|
||||
let name_str = get_event_name(event.event_type.as_str());
|
||||
let name = Ident::new(&name_str, span);
|
||||
let struct_name = Ident::new(&format!("{}Event", name_str), span);
|
||||
|
||||
if should_box_event(&name_str) {
|
||||
quote!(#name(Box<#struct_name>),)
|
||||
} else {
|
||||
quote!(#name(#struct_name),)
|
||||
}
|
||||
});
|
||||
|
||||
let event_types = events.iter().map(|event| {
|
||||
let name_str = get_event_name(event.event_type.as_str());
|
||||
let name = Ident::new(&name_str, span);
|
||||
|
||||
quote!(#name,)
|
||||
});
|
||||
|
||||
let type_from_names = events.iter().map(|event| {
|
||||
let name_str = event.event_type.as_str();
|
||||
let variant_name = Ident::new(&get_event_name(&name_str), span);
|
||||
|
||||
quote!(#name_str => GameEventType::#variant_name,)
|
||||
});
|
||||
|
||||
let type_to_names = events.iter().map(|event| {
|
||||
let name_str = event.event_type.as_str();
|
||||
let variant_name = Ident::new(&get_event_name(&name_str), span);
|
||||
|
||||
quote!(GameEventType::#variant_name => #name_str,)
|
||||
});
|
||||
|
||||
let to_types = events.iter().map(|event| {
|
||||
let name = get_event_name(event.event_type.as_str());
|
||||
let variant_name = Ident::new(&name, span);
|
||||
|
||||
quote!(GameEvent::#variant_name(_) => GameEventType::#variant_name,)
|
||||
});
|
||||
|
||||
let read_events = events.iter().map(|event| {
|
||||
let name = get_event_name(event.event_type.as_str());
|
||||
let variant_name = Ident::new(&name, span);
|
||||
let struct_name = Ident::new(&format!("{}Event", name), span);
|
||||
|
||||
if should_box_event(&name) {
|
||||
quote!(
|
||||
GameEventType::#variant_name => {
|
||||
GameEvent::#variant_name(Box::new(<#struct_name>::read(stream, definition)?))
|
||||
}
|
||||
)
|
||||
} else {
|
||||
quote!(
|
||||
GameEventType::#variant_name => {
|
||||
GameEvent::#variant_name(#struct_name::read(stream, definition)?)
|
||||
}
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
let write_events = events.iter().map(|event| {
|
||||
let name = get_event_name(event.event_type.as_str());
|
||||
let variant_name = Ident::new(&name, span);
|
||||
|
||||
quote!(
|
||||
GameEvent::#variant_name(event) => event.write(stream),
|
||||
)
|
||||
});
|
||||
|
||||
let sizes = events.iter().map(|event| {
|
||||
let name = get_event_name(event.event_type.as_str());
|
||||
let struct_name = Ident::new(&format!("{}Event", name), span);
|
||||
|
||||
quote!(
|
||||
(#name, std::mem::size_of::<#struct_name>())
|
||||
)
|
||||
});
|
||||
|
||||
quote!(
|
||||
#imports
|
||||
|
||||
fn read_value<'a, T: EventValue + BitRead<'a, LittleEndian> + Default>(
|
||||
stream: &mut Stream<'a>,
|
||||
entry: Option<&GameEventEntry>,
|
||||
name: &'static str,
|
||||
) -> Result<T> {
|
||||
let entry = match entry {
|
||||
Some(entry) => entry,
|
||||
None => {
|
||||
return Ok(T::default());
|
||||
}
|
||||
};
|
||||
if T::value_type() != entry.kind {
|
||||
return Err(ParseError::InvalidGameEvent {
|
||||
expected_type: T::value_type(),
|
||||
name,
|
||||
found_type: entry.kind,
|
||||
});
|
||||
}
|
||||
Ok(T::read(stream)?)
|
||||
}
|
||||
|
||||
#(#event_definitions)*
|
||||
|
||||
|
||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum GameEvent {
|
||||
#(#event_variants)*
|
||||
Unknown(RawGameEvent),
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum GameEventType {
|
||||
#(#event_types)*
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
impl Serialize for GameEventType {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for GameEventType {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let str = <&str>::deserialize(deserializer)?;
|
||||
Ok(GameEventType::from_type_name(str))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl GameEventType {
|
||||
pub fn from_type_name(name: &str) -> Self {
|
||||
match name {
|
||||
#(#type_from_names)*
|
||||
ty => GameEventType::Unknown(ty.into()),
|
||||
}
|
||||
}
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
#(#type_to_names)*
|
||||
GameEventType::Unknown(ty) => ty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEvent {
|
||||
pub fn read(stream: &mut Stream, definition: &GameEventDefinition) -> Result<Self> {
|
||||
Ok(match definition.event_type {
|
||||
#(#read_events)*
|
||||
GameEventType::Unknown(_) => GameEvent::Unknown(RawGameEvent::read(stream, definition)?),
|
||||
})
|
||||
}
|
||||
pub fn write(&self, stream: &mut BitWriteStream<LittleEndian>) -> bitbuffer::Result<()> {
|
||||
match &self {
|
||||
#(#write_events)*
|
||||
GameEvent::Unknown(raw) => raw.write(stream),
|
||||
}
|
||||
}
|
||||
pub fn event_type(&self) -> GameEventType {
|
||||
match &self {
|
||||
#(#to_types)*
|
||||
GameEvent::Unknown(raw) => raw.event_type.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_sizes() -> fnv::FnvHashMap<&'static str, usize> {
|
||||
[
|
||||
#(#sizes,)*
|
||||
].iter().copied().collect()
|
||||
}
|
||||
)
|
||||
}
|
||||
2
src/codegen/mod.rs
Normal file
2
src/codegen/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub mod gameevent;
|
||||
pub mod propnames;
|
||||
163
src/codegen/propnames.rs
Normal file
163
src/codegen/propnames.rs
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
use crate::demo::packet::datatable::{ParseSendTable, SendTableName, ServerClass};
|
||||
use crate::demo::parser::MessageHandler;
|
||||
use crate::demo::sendprop::{SendPropIdentifier, SendPropName};
|
||||
use crate::{Demo, DemoParser, MessageType, ParserState};
|
||||
use fnv::FnvHashMap;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use std::collections::HashMap;
|
||||
|
||||
struct PropInfo {
|
||||
identifier: SendPropIdentifier,
|
||||
table_name: String,
|
||||
prop_name: String,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct PropAnalyzer {
|
||||
prop_names: FnvHashMap<SendPropIdentifier, (SendTableName, SendPropName)>,
|
||||
}
|
||||
|
||||
impl MessageHandler for PropAnalyzer {
|
||||
type Output = Vec<PropInfo>;
|
||||
|
||||
fn does_handle(message_type: MessageType) -> bool {
|
||||
matches!(message_type, MessageType::PacketEntities)
|
||||
}
|
||||
|
||||
fn handle_data_tables(
|
||||
&mut self,
|
||||
parse_tables: &[ParseSendTable],
|
||||
_server_classes: &[ServerClass],
|
||||
_state: &ParserState,
|
||||
) {
|
||||
let mut numeric_tables: FnvHashMap<String, usize> = HashMap::default();
|
||||
for table in parse_tables {
|
||||
if table.props.iter().any(|prop| {
|
||||
prop.name == "lengthproxy" || prop.name.as_str().starts_with("lengthprop")
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
for prop_def in &table.props {
|
||||
self.prop_names.insert(
|
||||
prop_def.identifier(),
|
||||
(table.name.clone(), prop_def.name.clone()),
|
||||
);
|
||||
let name = prop_def.name.as_str();
|
||||
if name.len() == 3 && table.name.as_str().len() > 3 {
|
||||
if let Ok(_) = name.parse::<u8>() {
|
||||
let size = match table.name.as_str() {
|
||||
"m_nNextMapVoteOptions" => 3,
|
||||
"m_nStreaks"
|
||||
| "m_nNumNodeHillData"
|
||||
| "m_nModelIndexOverrides"
|
||||
| "m_nMinigameTeamScore"
|
||||
| "m_iTeamBaseIcons"
|
||||
| "m_iTeam"
|
||||
| "m_iNumTeamMembers"
|
||||
| "m_hProps"
|
||||
| "m_flNextRespawnWave"
|
||||
| "m_flEncodedController"
|
||||
| "m_eWinningMethod"
|
||||
| "m_chPoseIndex"
|
||||
| "m_bTrackAlarm"
|
||||
| "m_bTeamReady"
|
||||
| "m_bTeamCanCap"
|
||||
| "m_TeamRespawnWaveTimes" => 4,
|
||||
"m_nVoteOptionCount" => 5,
|
||||
"m_iWarnOnCap"
|
||||
| "m_iTeamInZone"
|
||||
| "m_iOwner"
|
||||
| "m_iControlPointParents"
|
||||
| "m_iCappingTeam"
|
||||
| "m_iCPGroup"
|
||||
| "m_hControlPointEnts"
|
||||
| "m_flUnlockTimes"
|
||||
| "m_flPathDistance"
|
||||
| "m_flLazyCapPerc"
|
||||
| "m_flCPTimerTimes"
|
||||
| "m_bInMiniRound"
|
||||
| "m_bCPLocked"
|
||||
| "m_bCPIsVisible"
|
||||
| "m_bCPCapRateScalesWithPlayers"
|
||||
| "m_bBlocked" => 8,
|
||||
"m_nAttachIndex" | "m_hAttachEntity" => 10,
|
||||
"m_nMannVsMachineWaveClassFlags"
|
||||
| "m_nMannVsMachineWaveClassFlags2"
|
||||
| "m_nMannVsMachineWaveClassCounts2"
|
||||
| "m_nMannVsMachineWaveClassCounts"
|
||||
| "m_bMannVsMachineWaveClassActive2"
|
||||
| "m_bMannVsMachineWaveClassActive" => 12,
|
||||
"m_chCurrentSlideLists" => 16,
|
||||
"m_bHillIsDownhill" => 20,
|
||||
"m_chAreaPortalBits" | "m_chAreaBits" => 24,
|
||||
"m_hMyWeapons" => 48,
|
||||
"m_iTeamReqCappers" | "m_iTeamOverlays" | "m_iTeamIcons" => 8 * 8,
|
||||
"m_flexWeight" | "m_flPoseParameter" => 96,
|
||||
_ => 65,
|
||||
};
|
||||
numeric_tables.insert(table.name.to_string(), size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (table, size) in numeric_tables {
|
||||
for num in 0..=size {
|
||||
let prop_name = SendPropName::from(format!("{:03}", num));
|
||||
self.prop_names.insert(
|
||||
SendPropIdentifier::new(&table, prop_name.as_str()),
|
||||
(table.clone().into(), prop_name),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn into_output(self, _state: &ParserState) -> Self::Output {
|
||||
let mut props: Vec<_> = self
|
||||
.prop_names
|
||||
.into_iter()
|
||||
.map(|(identifier, (table_name, prop_name))| PropInfo {
|
||||
identifier,
|
||||
table_name: table_name.to_string(),
|
||||
prop_name: prop_name.to_string(),
|
||||
})
|
||||
.collect();
|
||||
props.sort_by(|a, b| {
|
||||
a.table_name
|
||||
.cmp(&b.table_name)
|
||||
.then(a.prop_name.cmp(&b.prop_name))
|
||||
});
|
||||
props
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_prop_names(demo: Demo) -> TokenStream {
|
||||
let (_, props) = DemoParser::new_with_analyser(demo.get_stream(), PropAnalyzer::default())
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
let imports = quote!(
|
||||
use crate::demo::sendprop::SendPropIdentifier;
|
||||
);
|
||||
|
||||
let matches = props.into_iter().map(|prop| {
|
||||
let identifier: u64 = prop.identifier.into();
|
||||
let table_name = prop.table_name;
|
||||
let prop_name = prop.prop_name;
|
||||
quote! {
|
||||
#identifier => Some((#table_name, #prop_name))
|
||||
}
|
||||
});
|
||||
|
||||
quote!(
|
||||
#imports
|
||||
|
||||
pub fn get_prop_names(identifier: SendPropIdentifier) -> Option<(&'static str, &'static str)> {
|
||||
let identifier: u64 = identifier.into();
|
||||
match identifier {
|
||||
#(#matches,)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -9,6 +9,8 @@ pub use crate::demo::{
|
|||
Demo, Stream,
|
||||
};
|
||||
|
||||
#[cfg(feature = "codegen")]
|
||||
pub mod codegen;
|
||||
pub(crate) mod consthash;
|
||||
pub mod demo;
|
||||
pub(crate) mod nullhasher;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue