# ========= Copyright © Valve Corporation, All rights reserved. ==== #!perl use strict; # get the arguments if ( @ARGV < 2 ) { print( "\nUsage: export_entities.pl \n" ); exit; } my $filename = $ARGV[0]; my $outputname = $ARGV[1]; open( INFILE, "<", $filename ) || die "Couldn't open input file: $filename\n"; my @lines = ; close ( INFILE ); system( "p4 edit $outputname" ); open( OUTFILE, ">", $outputname ) || die "Couldn't open output file: $outputname\n"; my @entityTables; # Process the vmf and convert entity spawn keys into squirrel table format my @lines = ReadVMF( $filename ); my @entities = ExtractEntityBlocks( \@lines ); ProcessEntityBlocks( \@entities ); # Generate the output file foreach ( @entityTables ) { print( OUTFILE @$_ ); } close( OUTFILE ); ####################################### package EntityHash; sub new { my($class) = shift; my($hash) = shift; bless { "ents" => $hash }, $class } sub GetKeys { my $self = shift; return @{ $self->{'ents'}{ 'keys' } }; } sub GetValue { my ($self, $key) = @_; return $self->{'ents'}{ $key }; } sub SetValue { my ($self, $key, $value) = @_; $self->{'ents'}{ $key } = $value; my @keys = @{ $self->{'ents'}{ 'keys' } }; foreach ( @keys ) { if ( $_ eq $key ) { return; } } push @{ $self->{'ents'}{ 'keys' } }, $key; } package main; ####################################### sub ReadVMF { my $filename = shift; open( INFILE, "<", $filename ) || die "Couldn't open file: $filename\n"; my @file = ; close ( INFILE ); return @file; } ####################################### sub ExtractEntityBlocks { my $input = shift; my @entities; while ( @$input ) { my $line = shift @$input; if ( $line =~ /^\t*(\w+)$/ ) { my %result = ExtractBlockToHash( \@$input ); if ( $1 eq "entity" ) { push @entities, \%result; } } } return @entities; } ####################################### sub ProcessEntityBlocks { my $entities = shift; my $replacementValues = shift; foreach ( @$entities ) { my @entityBlock = ProcessEntityBlock( $_, $replacementValues ); if ( @entityBlock > 0 ) { push( @entityTables, [ @entityBlock ] ); } } } ####################################### sub ExtractBlockToArray { my $input = shift; my $depth = 0; my @newblock; do { my $line = shift @$input; if ( $line =~ /{/ ) { ++$depth; } elsif ( $line =~ /}/ ) { --$depth; } push( @newblock, $line ); } while ( $depth > 0 ); return @newblock; } ####################################### sub ExtractBlockToHash { my $input = shift; my $depth = 0; my %newblock; my @keys; do { my $line = shift @$input; if ( $line =~ /{/ ) { ++$depth; } elsif ( $line =~ /}/ ) { --$depth; } elsif ( $line =~ /^\t*(\w+)$/ ) { # sub-block within the entity my $key = $1; # FIXME: Can't trust that nested blocks have unique names, so for now just extract them into arrays my @result = ExtractBlockToArray( \@$input ); $newblock{ $key } = \@result; push @keys, $key; } elsif ( $line =~ /^\t*\"(\w+)\" \"(.+)\"$/ ) { # Key/Value pair my $key = $1; my $value = $2; $newblock{ $key } = $value; push @keys, $key; } } while ( $depth > 0 ); $newblock{ "keys" } = [ @keys ]; return %newblock; } ####################################### sub ProcessEntityBlock { my $EntData = new EntityHash( shift ); my $value; my $entname = "unnamed"; my $classname; my @spawnData; # Output the entity tables to the entity layer if ( $classname = $EntData->GetValue( "classname" ) ) { my $classnameChanged = 0; # Ignore some entities entirely if ( $classname eq "func_instance_parms" || $classname eq "func_detail" || $classname eq "info_overlay" || $classname eq "light_spot" ) { print( "Ignoring unsupported entity: $classname\n" ); return; } # convert some entities to their script version elsif ( $classname =~ /^(trigger.*)/ && $classname ne "trigger_finale" ) { $classname =~ s/trigger/script_trigger/; $classnameChanged = 1; } elsif ( $classname eq "func_nav_blocker" ) { $classname = "script_nav_blocker"; $classnameChanged = 1; } if ( $classnameChanged ) { my $prev = $EntData->GetValue( "classname" ); print( "Converting $prev to $classname\n" ); $EntData->SetValue( "classname", $classname ); } } else { print( "WARNING: Couldn't find entity classname!\n" ); return; } # Convert trigger brushes to extents if ( ($value = $EntData->GetValue( "solid" )) && (ref( $value ) eq "ARRAY") ) { if ( $classname =~ /^script_.*/ ) { my ($x, $y, $z) = ProcessBrushData( \@$value ); $EntData->SetValue( "\"extent\"", "\"$x $y $z\"" ); } else { print( "Stripping brush data from entity $classname ($entname)\n" ); } } # Output the final spawn table my @keys = $EntData->GetKeys(); foreach my $key ( @keys ) { # Ignore some keys if ( $key eq "id" ) { next; } $value = $EntData->GetValue( $key ); if ( ref( $value ) ne "ARRAY" ) { push( @spawnData, "\t\"$key\" \"$value\"\n" ); } } # Handle the connections block if ( ($value = $EntData->GetValue( "connections" )) && (ref( $value ) eq "ARRAY") ) { push( @spawnData, ExtractBlockToArray( \@$value ) ); } my @entityTable; push ( @entityTable, "\"entity\"\n" ); push ( @entityTable, "{\n" ); push ( @entityTable, @spawnData ); push ( @entityTable, "}\n" ); return @entityTable; } ####################################### sub ProcessBrushData { my $input = shift; my @mins = [ 0, 0, 0 ]; my @maxs = [ 0, 0, 0 ]; my @norms = [ 0, 0, 0 ]; my $init = 1; foreach my $line ( @$input ) { if ( $line =~ /^\t*\"plane\" \"(.*)\"/ ) { my $planes = $1; my @values = ( $planes =~ /([\d\-e\.]+)/g ); if ( $init ) { for ( my $i = 0; $i < 3; ++$i ) { $mins[$i] = $values[$i]; $maxs[$i] = $values[$i]; } $init = 0; } for ( my $vtx = 1; $vtx < 3; ++$vtx ) { for ( my $coord = 0; $coord < 3; ++$coord ) { my $test = @values[ $vtx * 3 + $coord ]; if ( $test < $mins[$coord] ) { $mins[$coord] = $test; } if ( $test > $maxs[$coord] ) { $maxs[$coord] = $test; } } } } } for ( my $i = 0; $i < 3; ++$i ) { $norms[$i] = ($maxs[$i] - $mins[$i]) / 2; } return @norms; }