1
0
forked from 0ad/0ad
0ad/source/tools/entity/Entity.pm
wraitii 1b844606aa Implement mul_round in checkrefs.
Missed in 19f600cfa2. In use since 2078abdcc4.

This was SVN commit r25247.
2021-04-12 16:33:54 +00:00

192 lines
4.8 KiB
Perl

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, $mod) = @_;
my $fn = "$vfsroot/$mod/simulation/templates/special/filter/$vfspath.xml";
if (not -e $fn) {
$fn = "$vfsroot/$mod/simulation/templates/mixins/$vfspath.xml";
}
if (not -e $fn) {
$fn = "$vfsroot/$mod/simulation/templates/$vfspath.xml";
}
return $fn;
}
sub get_file
{
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;
}
elsif ($op eq 'mul_round') {
# This is incorrect (floors instead of rounding)
# but for schema purposes it ought be fine.
$base->{' content'} = int($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, $mods, $base) = @_;
if ($vfspath =~ /\|/) {
my @paths = split(/\|/, $vfspath, 2);
$base = load_inherited($paths[1], $mods, $base);
$base = load_inherited($paths[0], $mods, $base);
return $base
}
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'}, $mods, $base);
apply_layer($parent->{Entity}, $layer->{Entity});
return $parent;
} else {
if (not $base) {
return $layer;
}
else {
apply_layer($base->{Entity}, $layer->{Entity});
return $base
}
}
}
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/$modName/simulation/templates/~~;
$n =~ s/\.xml$//;
push @files, $n;
};
find({ wanted => $find_process }, "$vfsroot/$modName/simulation/templates");
return @files;
}