Index: ps/trunk/source/tools/entity/Entity.pm =================================================================== --- ps/trunk/source/tools/entity/Entity.pm (revision 22095) +++ ps/trunk/source/tools/entity/Entity.pm (revision 22096) @@ -1,145 +1,168 @@ package Entity; use strict; use warnings; use XML::Parser; use Data::Dumper; use File::Find; my $vfsroot = '../../../binaries/data/mods'; sub get_filename { - my ($vfspath) = @_; - my $fn = "$vfsroot/public/simulation/templates/$vfspath.xml"; + my ($vfspath, $mod) = @_; + my $fn = "$vfsroot/$mod/simulation/templates/$vfspath.xml"; return $fn; } sub get_file { - my ($vfspath) = @_; - my $fn = get_filename($vfspath); + my ($vfspath, $mod) = @_; + my $fn = get_filename($vfspath, $mod); open my $f, $fn or die "Error loading $fn: $!"; local $/; return <$f>; } sub trim { my ($t) = @_; return '' if not defined $t; $t =~ /^\s*(.*?)\s*$/s; return $1; } sub load_xml { my ($vfspath, $file) = @_; my $root = {}; my @stack = ($root); my $p = new XML::Parser(Handlers => { Start => sub { my ($e, $n, %a) = @_; my $t = {}; die "Duplicate child node '$n'" if exists $stack[-1]{$n}; $stack[-1]{$n} = $t; for (keys %a) { $t->{'@'.$_}{' content'} = trim($a{$_}); } push @stack, $t; }, End => sub { my ($e, $n) = @_; $stack[-1]{' content'} = trim($stack[-1]{' content'}); pop @stack; }, Char => sub { my ($e, $str) = @_; $stack[-1]{' content'} .= $str; }, }); eval { $p->parse($file); }; if ($@) { die "Error parsing $vfspath: $@"; } return $root; } sub apply_layer { my ($base, $new) = @_; if ($new->{'@datatype'} and $new->{'@datatype'}{' content'} eq 'tokens') { my @old = split /\s+/, ($base->{' content'} || ''); my @new = split /\s+/, ($new->{' content'} || ''); my @t = @old; for my $n (@new) { if ($n =~ /^-(.*)/) { @t = grep $_ ne $1, @t; } else { push @t, $n if not grep $_ eq $n, @t; } } $base->{' content'} = join ' ', @t; } elsif ($new->{'@op'}) { my $op = $new->{'@op'}{' content'}; my $op1 = $base->{' content'}; my $op2 = $new->{' content'}; if ($op eq 'add') { $base->{' content'} = $op1 + $op2; } elsif ($op eq 'mul') { $base->{' content'} = $op1 * $op2; } else { die "Invalid operator '$op'"; } } else { $base->{' content'} = $new->{' content'}; } for my $k (grep $_ ne ' content', keys %$new) { if ($new->{$k}{'@disable'}) { delete $base->{$k}; } else { if ($new->{$k}{'@replace'}) { delete $base->{$k}; } $base->{$k} ||= {}; apply_layer($base->{$k}, $new->{$k}); delete $base->{$k}{'@replace'}; } } } +sub get_main_mod +{ + my ($vfspath, $mods) = @_; + my @mods_list = split(/\|/, $mods); + my $main_mod = $mods_list[0]; + my $fn = "$vfsroot/$main_mod/simulation/templates/$vfspath.xml"; + if (not -e $fn) + { + for my $dep (@mods_list) + { + $fn = "$vfsroot/$dep/simulation/templates/$vfspath.xml"; + if (-e $fn) + { + $main_mod = $dep; + last; + } + } + } + return $main_mod; +} + sub load_inherited { - my ($vfspath) = @_; - my $layer = load_xml($vfspath, get_file($vfspath)); + my ($vfspath, $mods) = @_; + my $main_mod = get_main_mod($vfspath, $mods); + my $layer = load_xml($vfspath, get_file($vfspath, $main_mod)); if ($layer->{Entity}{'@parent'}) { - my $parent = load_inherited($layer->{Entity}{'@parent'}{' content'}); + my $parent = load_inherited($layer->{Entity}{'@parent'}{' content'}, $mods); apply_layer($parent->{Entity}, $layer->{Entity}); return $parent; } else { return $layer; } } sub find_entities { + my ($modName) = @_; my @files; my $find_process = sub { return $File::Find::prune = 1 if $_ eq '.svn'; my $n = $File::Find::name; return if /~$/; return unless -f $_; - $n =~ s~\Q$vfsroot\E/public/simulation/templates/~~; + $n =~ s~\Q$vfsroot\E/$modName/simulation/templates/~~; $n =~ s/\.xml$//; push @files, $n; }; - find({ wanted => $find_process }, "$vfsroot/public/simulation/templates"); + find({ wanted => $find_process }, "$vfsroot/$modName/simulation/templates"); return @files; } Index: ps/trunk/source/tools/entity/checkrefs.pl =================================================================== --- ps/trunk/source/tools/entity/checkrefs.pl (revision 22095) +++ ps/trunk/source/tools/entity/checkrefs.pl (revision 22096) @@ -1,581 +1,677 @@ use strict; use warnings; use Data::Dumper; use File::Find; use XML::Simple; use JSON; +use Getopt::Long qw(GetOptions); +use lib "."; use Entity; -use constant CHECK_MAPS_XML => 0; -use constant ROOT_ACTORS => 1; +GetOptions ( + '--check-unused' => \(my $checkUnused = 0), + '--check-map-xml' => \(my $checkMapXml = 0), + '--validate-templates' => \(my $validateTemplates = 0), + '--mod-to-check=s' => \(my $modToCheck = "public") +); my @files; my @roots; my @deps; +# Force and checkMapXml if checkUnused is enabled to avoid false positives. +$checkMapXml |= $checkUnused; my $vfsroot = '../../../binaries/data/mods'; +my $supportedTextureFormats = 'dds|png'; +my $mods = get_mod_dependencies_string($modToCheck); +my $mod_list_string = $modToCheck; +if ($mods ne "") +{ + $mod_list_string = $mod_list_string."|$mods"; +} +$mod_list_string = $mod_list_string."|mod"; +print("Checking $modToCheck\'s integrity. \n"); +print("The following mod(s) will be loaded: $mod_list_string. \n"); +my @mods_list = split(/\|/, "$mod_list_string"); -sub vfs_to_physical +sub get_mod_dependencies { - my ($vfspath) = @_; - my $fn = "$vfsroot/public/$vfspath"; - if (not -e $fn) + my ($mod) = @_; + my $modjson = parse_json_file_full_path("$vfsroot/$mod/mod.json"); + my $modjsondeps = $modjson->{'dependencies'}; + for my $dep (@{$modjsondeps}) { - $fn = "$vfsroot/mod/$vfspath"; + # 0ad's folder isn't named like the mod. + if(index($dep, "0ad") != -1) + { + $dep = "public"; + } } - return $fn; + + return $modjsondeps; +} + +sub get_mod_dependencies_string +{ + my ($mod) = @_; + return join( '|',@{get_mod_dependencies($mod)}); +} + +sub vfs_to_physical +{ + my ($vfsPath) = @_; + my $fn = vfs_to_relative_to_mods($vfsPath); + return "$vfsroot/$fn"; } sub vfs_to_relative_to_mods { - my ($vfspath) = @_; - my $fn = "public/$vfspath"; - return $fn; + my ($vfsPath) = @_; + + for my $dep (@mods_list) + { + my $fn = "$dep/$vfsPath"; + + if (-e "$vfsroot/$fn") + { + return $fn; + } + } } sub find_files { - my ($vfspath, $extn) = @_; + my ($vfsPath, $extn) = @_; my @files; my $find_process = sub { return $File::Find::prune = 1 if $_ eq '.svn'; my $n = $File::Find::name; return if /~$/; return unless -f $_; return unless /\.($extn)$/; - $n =~ s~\Q$vfsroot\E/(public|mod)/~~; + $n =~ s~\Q$vfsroot\E/($mod_list_string)/~~; push @files, $n; }; - find({ wanted => $find_process }, "$vfsroot/public/$vfspath"); - find({ wanted => $find_process }, "$vfsroot/mod/$vfspath") if -d "$vfsroot/mod/$vfspath"; + + for my $dep (@mods_list) + { + find({ wanted => $find_process },"$vfsroot/$dep/$vfsPath") if -d "$vfsroot/$dep/$vfsPath"; + } return @files; } -sub parse_json_file +sub parse_json_file_full_path { my ($vfspath) = @_; - open my $fh, vfs_to_physical($vfspath) or die "Failed to open '$vfspath': $!"; + open my $fh, $vfspath or die "Failed to open '$vfspath': $!"; # decode_json expects a UTF-8 string and doesn't handle BOMs, so we strip those # (see http://trac.wildfiregames.com/ticket/1556) return decode_json(do { local $/; my $file = <$fh>; $file =~ s/^\xEF\xBB\xBF//; $file }); } +sub parse_json_file +{ + my ($vfspath) = @_; + return parse_json_file_full_path(vfs_to_physical($vfspath)) +} + sub add_entities { print "Loading entities...\n"; my @entfiles = find_files('simulation/templates', 'xml'); s~^simulation/templates/(.*)\.xml$~$1~ for @entfiles; for my $f (sort @entfiles) { my $path = "simulation/templates/$f.xml"; push @files, $path; - my $ent = Entity::load_inherited($f); + my $ent = Entity::load_inherited($f, "$mod_list_string"); push @deps, [ $path, "simulation/templates/" . $ent->{Entity}{'@parent'}{' content'} . ".xml" ] if $ent->{Entity}{'@parent'}; if ($f !~ /^template_/) { push @roots, $path; if ($ent->{Entity}{VisualActor}) { push @deps, [ $path, "art/actors/" . $ent->{Entity}{VisualActor}{Actor}{' content'} ] if $ent->{Entity}{VisualActor}{Actor}; push @deps, [ $path, "art/actors/" . $ent->{Entity}{VisualActor}{FoundationActor}{' content'} ] if $ent->{Entity}{VisualActor}{FoundationActor}; } if ($ent->{Entity}{Sound}) { my $gender = $ent->{Entity}{Identity}{Gender}{' content'} || "male"; my $lang = $ent->{Entity}{Identity}{Lang}{' content'} || "greek"; for (grep ref($_), values %{$ent->{Entity}{Sound}{SoundGroups}}) { # see simulation/components/Sound.js and Identity.js for explanation my $soundPath = $_->{' content'}; $soundPath =~ s/{gender}/$gender/g; $soundPath =~ s/{lang}/$lang/g; push @deps, [ $path, "audio/" . $soundPath ]; } } if ($ent->{Entity}{Identity}) { - push @deps, [ $path, "art/textures/ui/session/portraits/" . $ent->{Entity}{Identity}{Icon}{' content'} ] if $ent->{Entity}{Identity}{Icon}; + push @deps, [ $path, "art/textures/ui/session/portraits/" . $ent->{Entity}{Identity}{Icon}{' content'} ] if $ent->{Entity}{Identity}{Icon} and $ent->{Entity}{Identity}{Icon}{' content'} ne ''; + } + + if ($ent->{Entity}{Formation}) + { + push @deps, [ $path, "art/textures/ui/session/icons/" . $ent->{Entity}{Formation}{Icon}{' content'} ] if $ent->{Entity}{Formation}{Icon} and $ent->{Entity}{Formation}{Icon}{' content'} ne ''; } } } } +sub push_variant_dependencies +{ + my ($variant, $f) = @_; + push @deps, [ $f, "art/variants/$variant->{file}" ] if $variant->{file}; + push @deps, [ $f, "art/meshes/$variant->{mesh}" ] if $variant->{mesh}; + push @deps, [ $f, "art/particles/$variant->{particles}{file}" ] if $variant->{particles}{file}; + for my $tex (@{$variant->{textures}{texture}}) + { + push @deps, [ $f, "art/textures/skins/$tex->{file}" ] if $tex->{file}; + } + for my $prop (@{$variant->{props}{prop}}) + { + push @deps, [ $f, "art/actors/$prop->{actor}" ] if $prop->{actor}; + } + for my $anim (@{$variant->{animations}{animation}}) + { + push @deps, [ $f, "art/animation/$anim->{file}" ] if $anim->{file}; + } +} + sub add_actors { print "Loading actors...\n"; my @actorfiles = find_files('art/actors', 'xml'); for my $f (sort @actorfiles) { push @files, $f; - - push @roots, $f if ROOT_ACTORS; + push @roots, $f; my $actor = XMLin(vfs_to_physical($f), ForceArray => [qw(group variant texture prop animation)], KeyAttr => []) or die "Failed to parse '$f': $!"; for my $group (@{$actor->{group}}) { for my $variant (@{$group->{variant}}) { - push @deps, [ $f, "art/meshes/$variant->{mesh}" ] if $variant->{mesh}; - push @deps, [ $f, "art/particles/$variant->{particles}{file}" ] if $variant->{particles}{file}; - for my $tex (@{$variant->{textures}{texture}}) - { - push @deps, [ $f, "art/textures/skins/$tex->{file}" ] if $tex->{file}; - } - for my $prop (@{$variant->{props}{prop}}) - { - push @deps, [ $f, "art/actors/$prop->{actor}" ] if $prop->{actor}; - } - for my $anim (@{$variant->{animations}{animation}}) - { - push @deps, [ $f, "art/animation/$anim->{file}" ] if $anim->{file}; - } + push_variant_dependencies($variant, $f); } } push @deps, [ $f, "art/materials/$actor->{material}" ] if $actor->{material}; } } + +sub add_variants +{ + print "Loading variants...\n"; + my @variantfiles = find_files('art/variants', 'xml'); + + for my $f (sort @variantfiles) + { + push @files, $f; + push @roots, $f; + my $variant = XMLin(vfs_to_physical($f), ForceArray => [qw(texture prop animation)], KeyAttr => []) or die "Failed to parse '$f': $!"; + push_variant_dependencies($variant, $f); + } +} + sub add_art { print "Loading art files...\n"; - push @files, find_files('art/textures/particles', 'dds|png|jpg|tga'); - push @files, find_files('art/textures/terrain', 'dds|png|jpg|tga'); - push @files, find_files('art/textures/skins', 'dds|png|jpg|tga'); + push @files, find_files('art/textures/particles', $supportedTextureFormats); + push @files, find_files('art/textures/terrain', $supportedTextureFormats); + push @files, find_files('art/textures/skins', $supportedTextureFormats); push @files, find_files('art/meshes', 'pmd|dae'); push @files, find_files('art/animation', 'psa|dae'); } sub add_materials { print "Loading materials...\n"; my @materialfiles = find_files('art/materials', 'xml'); for my $f (sort @materialfiles) { push @files, $f; my $material = XMLin(vfs_to_physical($f), ForceArray => [qw(alternative)], KeyAttr => []); for my $alternative (@{$material->{alternative}}) { push @deps, [ $f, "art/materials/$alternative->{material}" ] if $alternative->{material}; } } } sub add_particles { print "Loading particles...\n"; my @particlefiles = find_files('art/particles', 'xml'); for my $f (sort @particlefiles) { push @files, $f; my $particle = XMLin(vfs_to_physical($f)); push @deps, [ $f, "$particle->{texture}" ] if $particle->{texture}; } } sub add_maps_xml { print "Loading maps XML...\n"; my @mapfiles = find_files('maps/scenarios', 'xml'); push @mapfiles, find_files('maps/skirmishes', 'xml'); + push @mapfiles, find_files('maps/tutorials', 'xml'); for my $f (sort @mapfiles) { - print " $f\n"; - push @files, $f; - push @roots, $f; my $map = XMLin(vfs_to_physical($f), ForceArray => [qw(Entity)], KeyAttr => []) or die "Failed to parse '$f': $!"; my %used; for my $entity (@{$map->{Entities}{Entity}}) { $used{$entity->{Template}} = 1; } for my $template (keys %used) { if ($template =~ /^actor\|(.*)$/) { # Handle special 'actor|' case push @deps, [ $f, "art/actors/$1" ]; } else { if ($template =~ /^resource\|(.*)$/) { # Handle special 'resource|' case $template = $1; } push @deps, [ $f, "simulation/templates/$template.xml" ]; } } # Map previews my $settings = decode_json($map->{ScriptSettings}); push @deps, [ $f, "art/textures/ui/session/icons/mappreview/" . $settings->{Preview} ] if $settings->{Preview}; } } sub add_maps_pmp { print "Loading maps PMP...\n"; # Need to generate terrain texture filename=>path lookup first my %terrains; for my $f (find_files('art/terrains', 'xml')) { $f =~ /([^\/]+)\.xml/ or die; # ignore terrains.xml if ($f !~ /terrains.xml$/) { warn "Duplicate terrain name '$1' (from '$terrains{$1}' and '$f')\n" if $terrains{$1}; $terrains{$1} = $f; } } my @mapfiles = find_files('maps/scenarios', 'pmp'); push @mapfiles, find_files('maps/skirmishes', 'pmp'); for my $f (sort @mapfiles) { push @files, $f; push @roots, $f; open my $fh, vfs_to_physical($f) or die "Failed to open '$f': $!"; binmode $fh; my $buf; read $fh, $buf, 4; die "Invalid PMP header ($buf) in '$f'" unless $buf eq "PSMP"; read $fh, $buf, 4; my $version = unpack 'V', $buf; die "Invalid PMP version ($version) in '$f'" unless $version == 6; read $fh, $buf, 4; my $datasize = unpack 'V', $buf; read $fh, $buf, 4; my $mapsize = unpack 'V', $buf; seek $fh, 2 * ($mapsize*16+1)*($mapsize*16+1), 1; # heightmap read $fh, $buf, 4; my $numtexs = unpack 'V', $buf; for (0..$numtexs-1) { read $fh, $buf, 4; my $len = unpack 'V', $buf; my $str; read $fh, $str, $len; push @deps, [ $f, $terrains{$str} || "art/terrains/(unknown)/$str" ]; } # ignore patches data } } sub add_soundgroups { print "Loading sound groups...\n"; my @soundfiles = find_files('audio', 'xml'); for my $f (sort @soundfiles) { push @files, $f; + push @roots, $f; my $sound = XMLin(vfs_to_physical($f), ForceArray => [qw(Sound)], KeyAttr => []) or die "Failed to parse '$f': $!"; my $path = $sound->{Path}; $path =~ s/\/$//; # strip optional trailing slash for (@{$sound->{Sound}}) { push @deps, [$f, "$path/$_" ]; } } } sub add_audio { print "Loading audio files...\n"; push @files, find_files('audio', 'ogg'); } sub add_gui_xml { print "Loading GUI XML...\n"; my @guifiles = find_files('gui', 'xml'); for my $f (sort @guifiles) { push @files, $f; if ($f =~ /^gui\/page_/) { push @roots, $f; my $xml = XMLin(vfs_to_physical($f), ForceArray => [qw(include)], KeyAttr => []) or die "Failed to parse '$f': $!"; for my $include (@{$xml->{include}}) { # If including an entire directory, find all the *.xml files if ($include =~ /\/$/) { push @deps, [ $f, $_ ] for find_files("gui/$include", 'xml'); } else { push @deps, [ $f, "gui/$include" ]; } } } else { my $xml = XMLin(vfs_to_physical($f), ForceArray => [qw(object script action sprite image)], KeyAttr => [], KeepRoot => 1) or die "Failed to parse '$f': $!"; my $name = (keys %$xml)[0]; if ($name eq 'objects' or $name eq 'object') { for (grep ref $_ , @{$xml->{objects}{script}}) { push @deps, [ $f, $_->{file} ] if $_->{file}; if ($_->{directory}) { # If including an entire directory, find all the *.js files push @deps, [ $f, $_ ] for find_files($_->{directory}, 'js') } } my $add_objects; $add_objects = sub { my ($parent) = @_; for my $obj (@{$parent->{object}}) { # TODO: look at sprites, styles, etc $add_objects->($obj); } }; $add_objects->($xml->{objects}); } elsif ($name eq 'setup') { # TODO: look at sprites, styles, etc } elsif ($name eq 'styles') { # TODO: look at sprites, styles, etc } elsif ($name eq 'sprites') { for my $sprite (@{$xml->{sprites}{sprite}}) { for my $image (@{$sprite->{image}}) { push @deps, [ $f, "art/textures/ui/$image->{texture}" ] if $image->{texture}; } } } else { print "Unexpected GUI XML root element '$name':\n" . Dumper $xml; exit; } } } } sub add_gui_data { print "Loading GUI data...\n"; push @files, find_files('gui', 'js'); - push @files, find_files('art/textures/ui', 'dds|png|jpg|tga'); + push @files, find_files('art/textures/ui', $supportedTextureFormats); + push @files, find_files('art/textures/selection', $supportedTextureFormats); } sub add_civs { print "Loading civs...\n"; my @civfiles = find_files('simulation/data/civs', 'json'); for my $f (sort @civfiles) { push @files, $f; push @roots, $f; my $civ = parse_json_file($f); - push @deps, [ $f, "art/textures/ui/" . $civ->{Emblem} ]; + push @deps, [ $f, "art/textures/ui/" . $civ->{Emblem} ] if $civ->{Emblem}; push @deps, [ $f, "audio/music/" . $_->{File} ] for @{$civ->{Music}}; } } sub add_rms { print "Loading random maps...\n"; push @files, find_files('maps/random', 'js'); my @rmsdefs = find_files('maps/random', 'json'); for my $f (sort @rmsdefs) { + next if $f =~ /^maps\/random\/rmbiome/; + push @files, $f; push @roots, $f; my $rms = parse_json_file($f); - - push @deps, [ $f, "maps/random/" . $rms->{settings}{Script} ]; + push @deps, [ $f, "maps/random/" . $rms->{settings}{Script} ] if $rms->{settings}{Script}; # Map previews push @deps, [ $f, "art/textures/ui/session/icons/mappreview/" . $rms->{settings}{Preview} ] if $rms->{settings}{Preview}; } } sub add_techs { print "Loading techs...\n"; my @techfiles = find_files('simulation/data/technologies', 'json'); for my $f (sort @techfiles) { push @files, $f; push @roots, $f; my $tech = parse_json_file($f); push @deps, [ $f, "art/textures/ui/session/portraits/technologies/" . $tech->{icon} ] if $tech->{icon}; push @deps, [ $f, "simulation/data/technologies/" . $tech->{supersedes} . ".json" ] if $tech->{supersedes}; } } +sub add_auras +{ + print "Loading auras...\n"; + + my @aurafiles = find_files('simulation/data/auras', 'json'); + for my $f (sort @aurafiles) + { + push @files, $f; + push @roots, $f; + + my $aura = parse_json_file($f); + + push @deps, [ $f, $aura->{overlayIcon} ] if $aura->{overlayIcon}; + + if($aura->{rangeOverlay}) + { + push @deps, [ $f, "art/textures/selection/" . $aura->{rangeOverlay}{lineTexture} ] if $aura->{rangeOverlay}{lineTexture}; + push @deps, [ $f, "art/textures/selection/" . $aura->{rangeOverlay}{lineTextureMask} ] if $aura->{rangeOverlay}{lineTextureMask}; + } + } +} + sub add_terrains { print "Loading terrains...\n"; my @terrains = find_files('art/terrains', 'xml'); for my $f (sort @terrains) { # ignore terrains.xml if ($f !~ /terrains.xml$/) { push @files, $f; + push @roots, $f; my $terrain = XMLin(vfs_to_physical($f), ForceArray => [qw(texture)], KeyAttr => []) or die "Failed to parse '$f': $!"; for my $texture (@{$terrain->{textures}{texture}}) { push @deps, [ $f, "art/textures/terrain/$texture->{file}" ] if $texture->{file}; } push @deps, [ $f, "art/materials/$terrain->{material}" ] if $terrain->{material}; } } } sub check_deps { my %files; @files{@files} = (); my %lcfiles; @lcfiles{map lc($_), @files} = @files; my %revdeps; for my $d (@deps) { push @{$revdeps{$d->[1]}}, $d->[0]; } for my $f (sort keys %revdeps) { next if exists $files{$f}; warn "Missing file '$f' referenced by: " . (join ', ', map "'$_'", map vfs_to_relative_to_mods($_), sort @{$revdeps{$f}}) . "\n"; if (exists $lcfiles{lc $f}) { warn "### Case-insensitive match (found '$lcfiles{lc $f}')\n"; } } } sub check_unused { my %reachable; @reachable{@roots} = (); my %deps; for my $d (@deps) { push @{$deps{$d->[0]}}, $d->[1]; } while (1) { my @newreachable; for my $r (keys %reachable) { push @newreachable, grep { not exists $reachable{$_} } @{$deps{$r}}; } last if @newreachable == 0; @reachable{@newreachable} = (); } for my $f (sort @files) { - next if exists $reachable{$f}; + next if exists $reachable{$f} + || index($f, "art/terrains/") != -1 + || index($f, "maps/random/") != -1 + || index($f, "art/materials/") != -1; warn "Unused file '" . vfs_to_relative_to_mods($f) . "'\n"; } } -add_maps_xml() if CHECK_MAPS_XML; - +add_maps_xml() if $checkMapXml; add_maps_pmp(); - add_entities(); - add_actors(); - +add_variants(); add_art(); - add_materials(); - add_particles(); - add_soundgroups(); add_audio(); - add_gui_xml(); add_gui_data(); - add_civs(); - add_rms(); - add_techs(); - add_terrains(); +add_auras(); -# TODO: add non-skin textures, and all the references to them - -print "\n"; check_deps(); -print "\n"; -check_unused(); +check_unused() if $checkUnused; +print "\n" if $checkUnused; +system("perl ../xmlvalidator/validate.pl") if $validateTemplates; Index: ps/trunk/source/tools/entity/creationgraph.pl =================================================================== --- ps/trunk/source/tools/entity/creationgraph.pl (revision 22095) +++ ps/trunk/source/tools/entity/creationgraph.pl (revision 22096) @@ -1,41 +1,42 @@ use strict; use warnings; +use lib "."; use Entity; -my @files = Entity::find_entities(); +my @files = Entity::find_entities("public"); my %files = map +($_ => 1), @files; open my $g, '>', 'creation.dot' or die $!; print $g "digraph G {\n"; for my $f (sort @files) { next if $f =~ /^template_/; print "# $f...\n"; - my $ent = Entity::load_inherited($f); + my $ent = Entity::load_inherited($f, "public"); if ($ent->{Entity}{Builder}) { my $ents = $ent->{Entity}{Builder}{Entities}{' content'}; $ents =~ s/\{civ\}/$ent->{Entity}{Identity}{Civ}{' content'}/eg; for my $b (split /\s+/, $ents) { warn "Invalid Builder reference: $f -> $b\n" unless $files{$b}; print $g qq{"$f" -> "$b" [color=green];\n}; } } if ($ent->{Entity}{TrainingQueue}) { my $ents = $ent->{Entity}{TrainingQueue}{Entities}{' content'}; $ents =~ s/\{civ\}/$ent->{Entity}{Identity}{Civ}{' content'}/eg; for my $b (split /\s+/, $ents) { warn "Invalid TrainingQueue reference: $f -> $b\n" unless $files{$b}; print $g qq{"$f" -> "$b" [color=blue];\n}; } } } print $g "}\n"; close $g; system("dot -Tpng creation.dot -o creation.png"); Index: ps/trunk/source/tools/entity/entvalidate.pl =================================================================== --- ps/trunk/source/tools/entity/entvalidate.pl (revision 22095) +++ ps/trunk/source/tools/entity/entvalidate.pl (revision 22096) @@ -1,72 +1,73 @@ use strict; use warnings; use XML::LibXML; +use lib "."; use Entity; my $rngschema = XML::LibXML::RelaxNG->new(location => '../../../binaries/system/entity.rng'); sub escape_xml { my ($t) = @_; $t =~ s/&/&/g; $t =~ s//>/g; $t =~ s/"/"/g; $t =~ s/\t/ /g; $t =~ s/\n/ /g; $t =~ s/\r/ /g; $t; } sub to_xml { my ($e) = @_; my $r = $e->{' content'}; $r = '' if not defined $r; for my $k (sort grep !/^[\@ ]/, keys %$e) { $r .= "<$k"; for my $a (sort grep /^\@/, keys %{$e->{$k}}) { $a =~ /^\@(.*)/; $r .= " $1=\"".escape_xml($e->{$k}{$a}{' content'})."\""; } $r .= ">"; $r .= to_xml($e->{$k}); $r .= ""; } return $r; } sub validate { my ($vfspath) = @_; my $xml = to_xml(Entity::load_inherited($vfspath)); my $doc = XML::LibXML->new->parse_string($xml); $rngschema->validate($doc); } sub check_all { - my @files = Entity::find_entities(); + my @files = Entity::find_entities("public"); my $count = 0; my $failed = 0; for my $f (sort @files) { next if $f =~ /^template_/; print "# $f...\n"; ++$count; eval { validate($f); }; if ($@) { ++$failed; print $@; eval { print to_xml(load_inherited($f)), "\n"; } } } print "\nTotal: $count; failed: $failed\n"; } check_all(); Index: ps/trunk/source/tools/entity/readme.md =================================================================== --- ps/trunk/source/tools/entity/readme.md (nonexistent) +++ ps/trunk/source/tools/entity/readme.md (revision 22096) @@ -0,0 +1,29 @@ +# Checkrefs.pl + +## Description + +This script checks the game files for missing dependencies, unused files, and for file integrity. If mods are specified, all their dependencies are also checked recursively. This script is particularly useful to detect broken actors or templates. + +## Requirements + +- Perl interpreter installed +- Dependencies: + - XML::Parser + - XML::Simple + - Getopt::Long + - File::Find + - Data::Dumper + - JSON + +## Usage + +- cd in source/tools/entity and run the script. + +``` +Usage: perl checkrefs.pl [OPTION]... +Checks the game files for missing dependencies, unused files, and for file integrity. + --check-unused check for all the unused files in the given mods and their dependencies. Implies --check-map-xml. Currently yields a lot of false positives. + --check-map-xml check maps for missing actor and templates. + --validate-templates run the validate.pl script to check if the xml files match their (.rng) grammar file. This currently only works for the public mod. + --mod-to-check=mods specify which mods to check. 'mods' should be a list of mods separated by '|'. Default value: 'public|mod'. +```