view Ikariam.pm @ 325:1f5cd5c4f6b6

build academy first
author "Rex Tsai <chihchun@kalug.linux.org.tw>"
date Wed, 11 Feb 2009 21:59:08 +0800
parents b3b845d30d4b
children 58b36b18809f
line wrap: on
line source


BEGIN {
    foreach (((getpwuid($<))[7], $ENV{HOME}, $ENV{LOGDIR}, ".")) {
        require "$_/.eagleeye.pm" if (-f "$_/.eagleeye.pm");
    }
}

package Ikariam::Base;
use strict;
require Class::DBI::SQLite;
use base qw(Class::DBI::SQLite);

sub _db_error {
    my ($self, %info) = @_;
    my $msg = delete $info{msg};
    die($msg);
# return $self->_carp($msg, %info);
}

1;

use Class::DBI::AutoLoader (
    dsn       => 'dbi:SQLite:dbname=ikariam.sqlite',
    options   => { RaiseError => 1 },
    tables    => ['cities', 'island', 'user', 'ally', 'report'],
    use_base  => 'Ikariam::Base',
    namespace => 'Ikariam',
);

package Ikariam::Extractor;
use strict;
use Data::Dumper;
use XML::LibXML;
use IO::Uncompress::Gunzip qw(gunzip $GunzipError) ;
use Carp;
use utf8;

sub new {
    Carp::croak("Options should be key/value pairs, not hash reference") if ref($_[1]) eq 'HASH'; 

    my($class, %conf) = @_;

    my $self = bless {
        doc => undef,
        gzip => 1,
    }, $class;

    $self->{gzip} = $conf{'gzip'} if(defined($conf{'gzip'}));
    $self->parse($conf{'content'}) if(defined($conf{'content'}));

    return $self;
}

sub parse {
    my ($self, $content) = @_;
    my $string;
    my $parser = XML::LibXML->new('1.0','UTF-8');

    if($self->{gzip} == 1) {
        gunzip \$content => \$string
            or die "gunzip failed: $GunzipError\n";
    } else {
        $string = $content;
    }

    # dirty hack for stupid highScore search function.
    $string =~ s/name="searchUser" value=".*?"/name="searchUser" value=""/;

    $self->{doc} = $parser->parse_html_string ($string, { suppress_errors => 1, encoding => 'UTF-8' });
    return;
}

# find($XPathQuery);
sub find {
    my $self = shift;
    my $query = shift;
    my $out = [];

    my $result = $self->{doc}->find($query);

    return undef unless defined($result);

    if ( $result->isa( 'XML::LibXML::NodeList' ) ) {
        if($result->size() == 0) {
            return undef unless wantarray;
            return ();
        }
        foreach ( @$result ) {
            # $_ is XML::LibXML::Element, XML::LibXML::Node
            my $literal = $_->to_literal();
            utf8::encode($literal);
            # warn $_->toString(1) , "\n";
            return $literal unless wantarray;
            push( @$out, $literal);
        }
    } else {
        Carp::croak("Unsupported data type"); 
    }
    # TODO
    # XML::LibXML::Literal
    # XML::LibXML::Boolean

    return @$out;
}

1;

package Ikariam;
use strict;
use Data::Dumper;
use LWP;

use HTTP::Cookies;
use WWW::Mechanize;
use XML::LibXML qw(:encoding);
use YAML qw/LoadFile Dump DumpFile/;
use IO::Uncompress::Gunzip qw(gunzip $GunzipError) ;
use POSIX;
use utf8;

sub new
{
    my ($class, $server, $user, $pass) = @_;

    my $self =
    {
        mech => WWW::Mechanize->new(
            agent => "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.3) Gecko/2008092816 Iceweasel/3.0.1 (Debian-3.0.1-1)",
            timeout => 10,
        ),
        server => $server,
        user => $user,
        pass => $pass,
        debug => undef,
    };


    # if debug
    LWP::Debug::level('+trace');

    $self->{mech}->cookie_jar(HTTP::Cookies->new(file => "/tmp/ikariam-$server-cookies.txt", autosave => 1, ignore_discard => 1));
    $self->{mech}->default_headers->push_header('Accept-Encoding', 'deflate');
 
    return bless $self, $class;
}

sub clone
{
    my $self = shift;
    my $copy = bless { %$self }, ref $self;  # copy most fields

    $copy;
}

sub viewAlly
{
    my $self = shift;
    my $allyId = shift;

    my $res = $self->{mech}->get(sprintf("http://%s/index.php?view=allyPage&allyId=%d", $self->{server}, $allyId));
    my $extractor = new Ikariam::Extractor(content => $res->content);

    my %ally;
    $ally{'id'} = $allyId;
    $ally{'name'} = $extractor->find("//table[\@id='allyinfo']/tr[1]/td[2]/text()");
    $ally{'members'} = $extractor->find("//table[\@id='allyinfo']/tr[2]/td[2]/text()");
    $ally{'score'} = $extractor->find("//table[\@id='allyinfo']/tr[4]/td[2]/text()");
    $ally{'score'} =~ s/\d+ \(([\d,]+)\)/$1/;
    $ally{'score'} =~ s/,//g;

    $ally{'url'} = $extractor->find("//table[\@id='allyinfo']/tr[5]/td[2]/text()");
    delete($ally{'url'}) if($ally{'url'} eq '-');

    return \%ally;
}

sub viewScore
{
    my $self = shift;
    my $type = shift || 'score';
    my $user = shift || '';
    my $offset = shift || 0;
    my %users;

    $self->{mech}->get(sprintf("http://%s/index.php?view=highscore&showMe=1", $self->{server}));
    my $res = $self->{mech}->submit_form(
        form_number => 1,
        fields      => {
            highscoreType => $type,
            offset => $offset,
            searchUser => $user,
        }
   );

    my $extractor = new Ikariam::Extractor(content => $res->content);
    my $result = $extractor->{doc}->find('//table[@class="table01"][2]//tr');

    foreach my $tr ( @$result ) {
        my %user;
        my $extractor = new Ikariam::Extractor(content => $tr->toString(0));

        my $href = $extractor->find('//td[@class="action"]/a/@href');
        if($href =~ /index\.php\?view=sendMessage&with=(\d+)&oldView=highscore/) {
            $user{'id'} = $1;
            # $user{'name'} = $user;
            # encoding issue.
            $user{'name'} = $extractor->find('//td[@class="name"]/text()');
            next if($user{'name'} eq '');

            $user{'ally'} = $extractor->find('//td[@class="allytag"]/a/text()');
            my $allyHref = $extractor->find('//td[@class="allytag"]/a/@href');
            if($allyHref =~ /\?view=allyPage&oldView=highscore&allyId=(\d+)/) {
                $user{'allyId'} = $1;
            }
            $user{$type} = $extractor->find('//td[@class="score"]/text()');
            $user{$type} =~ s/,//g;
            $users{$user{'id'}} = \%user;
        }
    }
    # print(Dumper(\%users));
    return \%users;
}

sub viewWorldMap
{
    my $self = shift;
    my $x = shift;
    my $y = shift;

    if(!defined($x) && !defined($y)) {
        die('location required');
    }

    my $res = $self->{mech}->post(sprintf("http://%s/index.php?view=worldmap_iso", $self->{server}), [
            xajax => 'getMapData',
            'xajaxargs[]' => $x,
            'xajaxargs[]' => $y,
            xajaxr => time,
            ]);

    my $c;
    my $status = gunzip \$res->content => \$c 
        or die "gunzip failed: $GunzipError\n";

    my @islands;
    # parsing xjxobj
    while($c =~ /<cmd n="jc" t="addToMap"><xjxobj><e><k>0<\/k><v><!\[CDATA\[(\d+)\]\]><\/v><\/e><e><k>1<\/k><v><!\[CDATA\[(\d+)\]\]><\/v><\/e><e><k>2<\/k><v><!\[CDATA\[(\d+)\]\]><\/v><\/e><e><k>3<\/k><v><!\[CDATA\[(\d+)\]\]><\/v><\/e><e><k>4<\/k><v><!\[CDATA\[(\d+)\]\]><\/v><\/e><e><k>5<\/k><v><!\[CDATA\[(\w+)\]\]><\/v><\/e><e><k>6<\/k><v><!\[CDATA\[(\d+)\]\]><\/v><\/e><e><k>7<\/k><v><!\[CDATA\[(\d+)\]\]><\/v><\/e><\/xjxobj><\/cmd>/g)
    {
        my %island;
        $island{id} = $3;
        $island{x} = $1;
        $island{y} = $2;
        $island{name} = $6;
        $island{tradegood} = $4;
        $island{wonder} = $5;
        # $7 ?
        $island{people} = $8;
        push @islands, \%island;
    }
    return @islands;
}

sub viewHomeMap
{
    my $self = shift;

    my $res = $self->{mech}->get(sprintf("http://%s/index.php?view=worldmap_iso", $self->{server}));

    my $c;
    my $status = gunzip \$res->content => \$c 
        or die "gunzip failed: $GunzipError\n";

    # m[50][36]=new Array(564,1,5,'Risietia', '5', 13);
    # x = 43-57 = 6
    # y = 27-41 = 6
    my @islands;
    while($c =~ /m\[(\d+)\]\[(\d+)\]=new Array\((\d+),(\d+),(\d+),'(\w+)', '(\d+)', (\d+)\);/g)
    {
        my %island;
        $island{id} = $3;
        $island{x} = $1;
        $island{y} = $2;
        $island{name} = $6;
        $island{tradegood} = $4;
        $island{wonder} = $5;
        # $7 ?
        $island{people} = $8;

        #foreach my $i (sort(keys(%island)))
        #{
        #    printf ("%s %s\n", $i, $island{$i});
        #}
        #print("\n");
        push @islands, \%island;
    }
    return @islands;
}

sub viewIsland
{
    my $self = shift;
    my $island = shift;

    my $res = $self->{mech}->get(sprintf("http://%s/index.php?view=island&id=%s", $self->{server}, $island));
    my $extractor = new Ikariam::Extractor(content => $res->content);

    my @cities;
    foreach my $i (0..16) {
        my $cityLocations = $extractor->find(sprintf('//li[@id="cityLocation%s"]/@class', $i));
        if($cityLocations =~ /city level(\d+)/) {
            my %info;
            $info{'citylevel'} = $1;
            $info{'cityname'} = $extractor->find(sprintf('//li[@id="cityLocation%s"]//li[@class="name"]/text()', $i));
            $info{'owner'} = $extractor->find(sprintf('//li[@id="cityLocation%s"]//li[@class="owner"]/text()', $i));
            $info{'owner'} =~ s/\s+//g;
            $info{'ally'} = $extractor->find(sprintf('//li[@id="cityLocation%s"]//li[@class="ally"]/a/text()', $i));
            if($info{'ally'} eq '-') {
                delete($info{'ally'}) 
            } else {
                my $href = $extractor->find(sprintf('//li[@id="cityLocation%s"]//li[@class="ally"]/a/@href', $i));
                if($href =~ /&allyId=(\d+)&/) {
                    $info{'allyId'} = $1;
                }
            }

            # Ally Id
            my $href = $extractor->find(sprintf('//li[@id="cityLocation%s"]//a[@class="messageSend"]/@href', $i));
            if ($href =~ /with=(\d+)&destinationCityId=(\d+)/) {
                $info{'user'} = $1;
                $info{'cityId'} = $2;
            } else {
                # 聯盟 this is me.
                my $id = $extractor->find(sprintf('//li[@id="cityLocation%s"]/a/@id', $i));
                if($id =~ /city_(\d+)/) {
                    $info{'user'} = undef; # FIXME
                    $info{'cityId'} = $1; 
                }
            }

            if(defined(($extractor->find(sprintf('//li[@id="cityLocation%s"]//span[@class="vacation"]', $i)))[0])) {
                $info{'status'} = 'v';
            }
            if(defined(($extractor->find(sprintf('//li[@id="cityLocation%s"]//span[@class="inactivity"]', $i)))[0])) {
                $info{'status'} = 'i';
            }
            push @cities, \%info;
        } else {
            # TODO: delete the old city.
        }

    }

    return @cities;
}

sub switchCityBySafehouseLevel {
    my $self = shift;
}

sub viewSendSpy {
    my $self = shift;
    my $cityId = shift;
    my $res = $self->{mech}->get(sprintf('http://%s/index.php?view=sendSpy&destinationCityId=%d', $self->{server}, $cityId));
    return Ikariam::Extractor->new(content => $res->content)->find(sprintf('//div[@class="percentage"]/text()'));
}

sub increaseTransporter {
    my $self = shift;
    my $param = shift;
    my $cityId = shift;

    # TODO: 應該扣除買船之黃金, 以免誤計
    # TODO: 加上所有之船隻
    # my @cargoCost = qw/160 244 396 812 1240 1272 1832 1888 3848 3972 5204 5384 6868 7120 8864 9200 11268 11712 14108 14680 23320 24288 28664 29880 34956 36468 42348 44212 51024 53308 61236 64024 73096 76468 87020 91088 103224 116524 122072 137432 180060 202132 211964 237444 249108 278276 292076 306623 321963 338138 355198 373191 392171 412195 433320 455612 479135 503962 530166 557828 587031 617863 650420 684802 721113 759466 799981 842783 888005 935790 986286 1039654 1096062 1155689 1218724 1285369 1355837 1430353 1509159 1592508 1680670 1773932 1872597 1976989 2087448 2204338 2328045 2458976 2597567 2744276 2899594 3064040 3238163 3422550 3617820 3824635 4043693 4275738 4521561 4782000 5057946 5350345 5660202 5988585 6336630 6705540 7096598 7511164 7950683 8416694 8910828 9434823 9990523 10579889 11205006 11868090 12571498 13317734 14109462 14949514/;
    # $city->{transporters}->{sum}
 
    # TODO: 找買船最便宜之城市
    my @locations = @{$self->{'cities'}->{$cityId}->{locations}};
    foreach (1..2) {
        if($locations[$_] eq 'port') {
            my $res = $self->{mech}->get(sprintf('http://%s/index.php?action=CityScreen&function=increaseTransporter&id=%s&position=%s', 
                    $self->{server}, $cityId, $_));
        }
    }
}


# for tavern only
sub set {
    my $self = shift;
    my $type = shift;
    my $cityId = shift;

    die ("we don't know about this city") unless(defined($self->{'cities'}->{$cityId}));

    my $position = -1;
    my @locations = @{$self->{'cities'}->{$cityId}->{locations}};
    foreach (0..$#locations) {
        $position = $_ if($locations[$_] eq $type);
    }

# Academy - inputScientists
    if($position != -1 && $type eq 'academy') {
        $self->{mech}->get (sprintf("http://%s/index.php?view=%s&id=%s&position=%d", $self->{server}, $type, $cityId, $position));
        $self->{mech}->submit_form(
                form_number => 1,
                fields      => {
                    s => $self->{'cities'}->{$cityId}->{$type}->{maxValue},
                }
                );

    }

# Tavern
    if($position != -1 && $type eq 'tavern') {
        $self->{mech}->get (sprintf("http://%s/index.php?view=%s&id=%s&position=%d", $self->{server}, $type, $cityId, $position));
        $self->{mech}->submit_form(
                form_number => 1,
                fields      => {
                    amount => $self->{'cities'}->{$cityId}->{$type}->{maxValue},
                }
                );

    }
}

sub is_shipyard_upgrading {
    my $self = shift;
    my $cityId = shift;
    my $type = "shipyard";

    my $position = -1;
    my @locations = @{$self->{'cities'}->{$cityId}->{locations}};
    foreach (1..2) {
        $position = $_ if($locations[$_] eq $type);
    }

    if($position != -1) {
        my $res = $self->{mech}->get (sprintf("http://%s/index.php?view=%s&id=%s&position=%d", $self->{server}, $type, $cityId, $position));
        if(defined(Ikariam::Extractor->new(content => $res->content)->find('//div[@class="isUpgrading"]'))) {
            return 1;
        } else {
            return 0;
        }
    }
    return 0;
}

sub is_navy_trainning {
    my $self = shift;
    my $cityId = shift;
    my $type = "shipyard";

    my $position = -1;
    my @locations = @{$self->{'cities'}->{$cityId}->{locations}};
    foreach (1..2) {
        $position = $_ if($locations[$_] eq $type);
    }

    if($position != -1) {
        my $res = $self->{mech}->get (sprintf("http://%s/index.php?view=%s&id=%s&position=%d", $self->{server}, $type, $cityId, $position));
        if(defined(Ikariam::Extractor->new(content => $res->content)->find('//div[@id="unitConstructionList"]//h4'))) {
            return 1;
        } else {
            return 0;
        }
    }
    # FIXME we can not found the shipyard
    return 0;
}

sub buildShips {
    my $self = shift;
    my $shipType = shift;
    my $cityId = shift;
    my $type = 'shipyard';

    warn("buildShips $shipType");
    my $position = -1;
    my @locations = @{$self->{'cities'}->{$cityId}->{locations}};
    foreach (1..2) {
        $position = $_ if($locations[$_] eq $type);
    }

    if($position != -1) {
        my $res = $self->{mech}->get (sprintf("http://%s/index.php?view=%s&id=%s&position=%d", $self->{server}, $type, $cityId, $position));
        $self->{mech}->submit_form(
                form_number => 1,
                fields      => {
                    $shipType => 1,
                }
                );
    }
}

sub is_army_trainning {
    my $self = shift;
    my $cityId = shift;
    my $type = "barracks";

    my $position = -1;
    my @locations = @{$self->{'cities'}->{$cityId}->{locations}};
    foreach (2..$#locations) {
        $position = $_ if($locations[$_] eq $type);
    }

    if($position != -1) {
        my $res = $self->{mech}->get (sprintf("http://%s/index.php?view=%s&id=%s&position=%d", $self->{server}, $type, $cityId, $position));
        if(defined(Ikariam::Extractor->new(content => $res->content)->find('//div[@id="unitConstructionList"]//h4'))) {
            return 1;
        } else {
            return 0;
        }
    }
    # FIXME we can not found the shipyard
    return 0;
}

sub is_barracks_upgrading {
    my $self = shift;
    my $cityId = shift;
    my $type = 'barracks';

    my $position = -1;
    my @locations = @{$self->{'cities'}->{$cityId}->{locations}};
    foreach (2..$#locations) {
        $position = $_ if($locations[$_] eq $type);
    }

    if($position != -1) {
        my $res = $self->{mech}->get (sprintf("http://%s/index.php?view=%s&id=%s&position=%d", $self->{server}, $type, $cityId, $position));
        if(defined(Ikariam::Extractor->new(content => $res->content)->find('//div[@class="isUpgrading"]'))) {
            return 1;
        } else {
            return 0;
        }
    }
    return 0;
}

sub buildUnits {
    my $self = shift;
    my $unitType = shift;
    my $cityId = shift;
    my $type = 'barracks';

    warn("buildShips $unitType");
    my $position = -1;
    my @locations = @{$self->{'cities'}->{$cityId}->{locations}};
    foreach (2..$#locations) {
        $position = $_ if($locations[$_] eq $type);
    }

    if($position != -1) {
        my $res = $self->{mech}->get (sprintf("http://%s/index.php?view=%s&id=%s&position=%d", $self->{server}, $type, $cityId, $position));
        $self->{mech}->submit_form(
                form_number => 1,
                fields      => {
                    $unitType => 1,
                }
                );
    }
}

sub buildSpy {
    my $self = shift;
    my $unitType = shift;
    my $cityId = shift;
    my $type = 'safehouse';

    my $position = -1;
    my @locations = @{$self->{'cities'}->{$cityId}->{locations}};
    foreach (2..$#locations) {
        $position = $_ if($locations[$_] eq $type);
    }

    if($position != -1) {
        my $res = $self->{mech}->get (sprintf("http://%s/index.php?view=%s&id=%s&position=%d", $self->{server}, $type, $cityId, $position));
        my $url = 
            Ikariam::Extractor->new(content => $res->content)->find(sprintf('//div[@class="forminput"]/a/@href'));
        $self->{mech}->get($url) if(defined($url));
    }
}

sub build {
    my $self = shift;
    my $type = shift;
    my $cityId = shift;

    die ("we don't know about this city") unless(defined($self->{'cities'}->{$cityId}));

    my $position = -1;
    my @locations = @{$self->{'cities'}->{$cityId}->{locations}};
    foreach (0..$#locations) {
        $position = $_ if($locations[$_] eq $type);
    }

    if($position == -1) {
        my $targetPosition = undef;
        if($type eq "wall") {
            # 14 is wall.
            $targetPosition = 14;
        } else {
            foreach (0..13) {
                next if($_ <= 2 && ($type ne "workshop-fleet" &&
                            $type ne "shipyard"));

                if($locations[$_] eq undef) {
                    $targetPosition = $_;
                    last;
                }
            }
        }
        
        my $res = $self->{mech}->get(sprintf('http://%s/index.php?view=buildingGround&id=%s&position=%s', 
                        $self->{server}, $cityId, $targetPosition));
        my $url = 
            Ikariam::Extractor->new(content => $res->content)->find(sprintf('//li[@class="building %s"]//a/@href', $type));
        $self->{mech}->get($url) if(defined($url));
    } else {
        my $res = $self->{mech}->get (sprintf("http://%s/index.php?view=%s&id=%s&position=%d", $self->{server}, $type, $cityId, $position));
        my $url = Ikariam::Extractor->new(content => $res->content)->find('//a[@title="升級建築物"]/@href');
        $self->{mech}->get($url) if(defined($url));
    }
}

sub run {
    my $self = shift;
    my $param = shift;
    # defense.
    warn ("run $param not implemented yet.");
}

sub research
{
    my $self = shift;
    my $type = shift;
    my $cityId = shift;

    # check if we are researching the same stuff
    my $res =  $self->{mech}->get(sprintf('http://%s/index.php?action=CityScreen&function=changeResearch&id=%s&researchType=%s', $self->{server}, $cityId, $type));
}

sub checkResearch {
    my $self = shift;
    my $cityId = shift;

    my $res = $self->{mech}->get(sprintf('http://%s/index.php?view=researchOverview&id=%s', $self->{server}, $cityId));
    
    my @urls = Ikariam::Extractor->new(content => $res->content)->find('//ul[@class="explored"]//a/@href');
    my $out = {};
    foreach(@urls) {
        if(/view=researchDetail&id=\d+&position=\d+&researchId=(\d+)$/) {
            # we ignore the chinese name of technology researched.
            @$out{$1} = 1;
        }
    }
    return $out;
}

sub plunderCity {
    my $self = shift;
    my $cityId = shift;
    my $fields = shift;
    my $res = $self->{mech}->get(sprintf("http://%s/index.php?view=plunder&destinationCityId=%d", $self->{server}, $cityId));

    my $extractor = Ikariam::Extractor->new(content => $res->content);
    # check peace treaty
    my $treaty = $extractor->find('//div[@class="warning"]');
    if(!defined($treaty)) {
        my @forms = $self->{mech}->forms();
        if($#forms < 1) {
            my $city = Ikariam::Cities->retrieve($cityId);
            $city->delete;
        } else {
            $self->{mech}->submit_form(
                    form_number => 1,
                    fields      => $fields);
        }
    } else {
        # put the id in the friends.txt file.
        Ikariam::Cities->has_a(user => 'Ikariam::User');
        my $city = Ikariam::Cities->retrieve($cityId);
        my $sheep = $city->user;

        open(OUT, ">>friends.txt") or Carp::carp("can not open friends.txt");
        print OUT $sheep->name . "\n";
        close(OUT);
        Carp::carp($treaty);
    }
}


sub blockadeCity {
    my $self = shift;
    my $cityId = shift;
    my $fields = shift;
    my $res = $self->{mech}->get(sprintf("http://%s/index.php?view=blockade&destinationCityId=%d", $self->{server}, $cityId));

    my $extractor = Ikariam::Extractor->new(content => $res->content);
    # check peace treaty
    my $treaty = $extractor->find('//div[@class="warning"]');
    if(!defined($treaty)) {
        my @forms = $self->{mech}->forms();
        if($#forms < 1) {
            my $city = Ikariam::Cities->retrieve($cityId);
            $city->delete;
        } else {
            $self->{mech}->submit_form(
                    form_number => 1,
                    fields      => $fields);
        }
    } else {
        # put the id in the friends.txt file.
        Ikariam::Cities->has_a(user => 'Ikariam::User');
        my $city = Ikariam::Cities->retrieve($cityId);
        my $sheep = $city->user;

        open(OUT, ">>friends.txt") or Carp::carp("can not open friends.txt");
        print OUT $sheep->name . "\n";
        close(OUT);
        Carp::carp($treaty);
    }
}

sub changeCity {
    my $self = shift;
    my $cityId = shift;

    $self->{mech}->get(sprintf("http://%s/index.php", $self->{server}));
    $self->{mech}->submit_form(
        form_number => 2,
        fields      => {
            cityId => $cityId,
        }
    );
}

sub readCity {
    my $self = shift;
    my $cityId = shift;
    my $data;

    my $res = $self->{mech}->get(sprintf("http://%s/index.php?view=city&id=%d", $self->{server}, $cityId));
    my $extractor = new Ikariam::Extractor(content => $res->content);

    foreach my $i (0..14) {
        my @buildings = $extractor->find(sprintf('//li[@id="position%s"]/@class', $i));
        foreach my $building (@buildings) {
            if (!($building =~ /buildingGround/) && !($building =~ /townhall/)) {
                $data->{locations}[$i] = $building;

                my $span;
                my @spans = $extractor->find(sprintf('//li[@id="position%s"]//span[@class="textLabel"]/text()', $i));
                if($#spans >= 1) {
                    $span = $spans[1];
                } else {
                    $span = $spans[0];
                }
                my (undef, undef, $level) = split(/ /, $span);
                $data->{buildings}->{$building} = $level;
            }
        }
    }

    $data->{id} = $cityId;
    $data->{name} = $extractor->find('//span[@class="city"]/text()');

    my $island = $extractor->find('//div[@id="breadcrumbs"]/a[@class="island"]');
    if($island =~ /(\w+)\[(\d+):(\d+)\]/) {
        $data->{island}->{name} = $1;
        $data->{island}->{x} = $2;
        $data->{island}->{y} = $3;
        $data->{island}->{id} = my $island = $extractor->find('//div[@id="breadcrumbs"]/a[@class="island"]/@href');
        $data->{island}->{id} =~ s/\?view=island&id=(\d+)/$1/;
    }

    return $data;
}

sub checkCity {
    my $self = shift;
    my $cityId = shift;

    $self->{'cities'}->{$cityId} = $self->readCity($cityId);

    my $res = $self->{mech}->get(sprintf("http://%s/index.php?view=city&id=%d", $self->{server}, $cityId));
    my $extractor = new Ikariam::Extractor(content => $res->content);

    $self->{'cities'}->{$cityId}->{resources}->{gold} = $extractor->find('//span[@id="value_gold"]/text()');
    $self->{'cities'}->{$cityId}->{resources}->{gold} =~ s/,//g;
    
    $self->{'cities'}->{$cityId}->{construction} = defined($extractor->find('//*[@class="constructionSite"]')) ? 1 : 0;

    # maxCapacity
    my $page = $extractor->{doc}->toString(1);
    if($page =~ /maxCapacity : {\s+wood: (\d+),\s+wine: (\d+),\s+marble: (\d+),\s+crystal: (\d+),\s+sulfur: (\d+)\s+}/s) {
        $self->{'cities'}->{$cityId}->{maxCapacity}->{wood} = $1;
        $self->{'cities'}->{$cityId}->{maxCapacity}->{wine} = $2;
        $self->{'cities'}->{$cityId}->{maxCapacity}->{marble} = $3;
        $self->{'cities'}->{$cityId}->{maxCapacity}->{crystal} = $4;
        $self->{'cities'}->{$cityId}->{maxCapacity}->{sulfur} = $5;
    }

    foreach my $good (qw/wood wine marble crystal sulfur/) {
        $self->{'cities'}->{$cityId}->{resources}->{$good} = $extractor->find(sprintf('//span[@id="value_%s"]', $good));
        $self->{'cities'}->{$cityId}->{resources}->{$good} =~ s/,//g;
    }

    $self->{'cities'}->{$cityId}->{transporters}->{avail} = $extractor->find('//span[@id="value_transAvail"]/text()');
    $self->{'cities'}->{$cityId}->{transporters}->{sum} = $extractor->find('//span[@id="value_transSum"]/text()');
    $self->{'cities'}->{$cityId}->{transporters}->{sum} =~ s/[\(|\)]//g;
    $self->{'cities'}->{$cityId}->{maxActionPoints} = $extractor->find('//span[@id="value_maxActionPoints"]');;
}

sub checkAcademy {
    my $self = shift;
    my $cityId = shift;
    my $building = "academy";

    my @locations = @{$self->{'cities'}->{$cityId}->{locations}};
    # init
    $self->{'cities'}->{$cityId}->{$building}->{'maxValue'} = 0;
    $self->{'cities'}->{$cityId}->{$building}->{'iniValue'} = 0;
    foreach (0..$#locations) {
        if($locations[$_] eq $building) {
            my $res = $self->{mech}->get(sprintf('http://%s/index.php?view=%s&id=%d&position=%d', $self->{server}, $building, $cityId, $_ ));
            my $extractor = Ikariam::Extractor->new(content => $res->content);
            if($extractor->{doc}->toString(0) =~ /maxValue : (\d+),\s+overcharge : \d+,\s+iniValue : (\d+),/) {
                $self->{'cities'}->{$cityId}->{$building}->{'maxValue'} = $1;
                $self->{'cities'}->{$cityId}->{$building}->{'iniValue'} = $2;
            }
            last;
        }
    }
}

sub checkTavern {
    my $self = shift;
    my $cityId = shift;

    my @locations = @{$self->{'cities'}->{$cityId}->{locations}};
    # init
    $self->{'cities'}->{$cityId}->{'tavern'}->{'maxValue'} = 0;
    $self->{'cities'}->{$cityId}->{'tavern'}->{'iniValue'} = 0;
    foreach (0..$#locations) {
        if($locations[$_] eq 'tavern') {
            my $res = $self->{mech}->get(sprintf('http://%s/index.php?view=tavern&id=%d&position=%d', $self->{server}, $cityId, $_ ));
            my $extractor = Ikariam::Extractor->new(content => $res->content);
            if($extractor->{doc}->toString(0) =~ /maxValue : (\d+),\s+overcharge : \d+,\s+iniValue : (\d+),/) {
                $self->{'cities'}->{$cityId}->{'tavern'}->{'maxValue'} = $1;
                $self->{'cities'}->{$cityId}->{'tavern'}->{'iniValue'} = $2;
            }
            last;
        }
    }
}

sub checkSafeHouse {
    my $self = shift;
    my $cityId = shift;
    my $data;

    my @locations = @{$self->{'cities'}->{$cityId}->{locations}};
    foreach (0..$#locations) {
        if($locations[$_] eq 'safehouse') {
            my $res = $self->{mech}->get(sprintf('http://%s/index.php?view=safehouse&id=%d&position=%d', $self->{server}, $cityId, $_ ));
            my $extractor = Ikariam::Extractor->new(content => $res->content);
            foreach (1..25) {
                my @links = $extractor->find(sprintf('//div[@class="spyinfo" and position() = %d]//a/@href', $_));
                if ($links[0] =~ /id=(\d+)/) {
                    $data->{$1}->{city} = $self->readCity($1);
                    @{$data->{$1}->{risks}} = Ikariam::Extractor->new(content => $self->{mech}->get($links[1])->content)->find('//div[@class="missionRisk"]');
                }
            }
            last;
        }
    }
    return $data;
}


sub checkMilitaryAdvisorMilitaryMovements  {
    my $self = shift;
    my $res = $self->{mech}->get(sprintf('http://%s/index.php?view=militaryAdvisorMilitaryMovements', $self->{server}));
    my $extractor = new Ikariam::Extractor(content => $res->content);

    $self->{'military'}->{wars} = 0;
    $self->{'military'}->{attack} = 0;

    foreach (qw/homeland elsewhere/) {
        $self->{'military'}->{$_} = ();
        my $result = $extractor->{doc}->find(sprintf('//div[@id="%s"]//ul[@id="battleReports"]/li[@class="enroute"]', $_));
        foreach my $div ( @$result ) {
            my $extractor = new Ikariam::Extractor(content => $div->toString(1));
            my $f = $extractor->find('//div[@class="report"]/a[1]/@href');
            my $t = $extractor->find('//div[@class="report"]/a[2]/@href');
            $f = $1 if($f =~ /\?view=island&cityId=(\d+)/);
            $t = $1 if($t =~ /\?view=island&cityId=(\d+)/);

            push @{$self->{'military'}->{$_}}, { from => $f, to => $t};
            if($_ eq 'homeland') {
                $self->{'military'}->{wars}++;
            } else {
                $self->{'military'}->{attack}++;
            }
        }
    }
}

sub checkMilitaryAdvisorReportView {
    my $self = shift;
    my $combatId = shift;
    my $res = $self->{mech}->get(sprintf('http://%s/index.php?view=militaryAdvisorReportView&detailedCombatId=%s', $self->{server}, $combatId));
    my $extractor = new Ikariam::Extractor(content => $res->content);

    my %report;

    $report{id} = $combatId;
    my $c = $extractor->{doc}->toString(0);
    
    # FIXME 城鎮  6 級)的城牆( 2 級)為防禦部隊增加了 7% 的防禦力。
    while($c =~ /城鎮\s+(\d+)/gs) {
        $report{cityLevel} = $1;
        $report{wallLevel} = $2;
    }
    
    $report{attacker} = $extractor->find('//div[@id="troopsReport"]//table[@id="result"]//th[@class="attackers"]');
    $report{defender} = $extractor->find('//div[@id="troopsReport"]//table[@id="result"]//th[@class="defenders"]');
    $report{winner} = $extractor->find('//div[@id="troopsReport"]//table[@id="result"]//td[@class="winner"]');

    # the combat we win!
    $report{targetCity} = $extractor->find('//div[@id="troopsReport"]/div/h3/a/text()');
    my $href = $extractor->find('//div[@id="troopsReport"]/div/h3/a/@href');
    if(!defined($href)) {
        # the combat we lost!
        $report{targetCity} = $extractor->find('//td[@class="battle"]/a/text()');
        $href = $extractor->find('//td[@class="battle"]/a/@href');
    }

    if($href =~ /index\.php\?view=island&id=(\d+)&selectCity=(\d+)/) {
       $report{island} = $1;
       $report{city} = $2;
    } else {
        warn($href);
        warn ("can not read combat reprot $combatId");
        return undef;
    }
    $report{gold} = $extractor->find('//div[@id="troopsReport"]//ul[@class="resources"]/li[@class="gold"]');
    $report{wood} = $extractor->find('//div[@id="troopsReport"]//ul[@class="resources"]/li[@class="wood"]');
    $report{crystal} = $extractor->find('//div[@id="troopsReport"]//ul[@class="resources"]/li[@class="glass"]');
    $report{wine} = $extractor->find('//div[@id="troopsReport"]//ul[@class="resources"]/li[@class="wine"]');
    $report{sulfur} = $extractor->find('//div[@id="troopsReport"]//ul[@class="resources"]/li[@class="sulfur"]');

    foreach(qw/gold wood crystal wine sulfur attacker defender winner/) {
        $report{$_} =~ s/^.*?:\s+//;
        $report{$_} =~ s/\s+$//;
        $report{$_} =~ s/,//g;
    }
    return \%report;
}

sub checkMilitaryAdvisorCombatReports {
    my $self = shift;
    my $page = shift || 0;

    my $res = $self->{mech}->get(sprintf('http://%s/index.php?view=militaryAdvisorCombatReports&start=%s', $self->{server}, $page));
    my $extractor = new Ikariam::Extractor(content => $res->content);

    my $result = $extractor->{doc}->find('//div[@id="troopsOverview"]//table/tr[position()<=10]');
    foreach my $tr ( @$result ) {
        my $trExtractor = new Ikariam::Extractor(content => $tr->toString(1));
        my $date = $trExtractor->find('//td[@class="date"]');
        $date =~ s/^\s+//g; $date =~ s/\s+$//g;


        my $href = $trExtractor->find('//td/a/@href');
        if($href =~ /index.php\?view=militaryAdvisorReportView&combatId=(\d+)/) {
            my $report = Ikariam::Report->retrieve($1);
            if(!$report) {
                my $report_hash = $self->checkMilitaryAdvisorReportView($1);
                if(defined($report_hash)) {
                    $report = Ikariam::Report->insert($report_hash);

                    if($date =~ /(\d+)\.(\d+)\. (\d+):(\d+)/) {
                        my $unixtime = mktime (0, $4, $3, $1, ($2-1), '108');
                        $report->set('date', $unixtime);
                    }
                    $report->set('time', time);

                    $report->update();
                } else {
                    # for some reason, we can not read the report.
                    next;
                }
            } else {
                # we have found the report we like to know, exit the function.
                return;
            }
        } else {
            # there is not report yet.
            return;
        }
    }

    my @moreCombats = $extractor->find('//div[@id="troopsOverview"]//table/tr[position()>10]//a/@href');
    foreach (@moreCombats){
        last if(/^javascript/);
        if(/\?view=militaryAdvisorCombatReports&start=(\d+)/) {
            next if($1 le $page);
            $self->checkMilitaryAdvisorCombatReports($1);
            last;
        }
    }
}

sub checkTownHall {
    my $self = shift;
    my $cityId = shift;

    my $res = $self->{mech}->get(sprintf('http://%s/index.php?view=townHall&id=%d', $self->{server}, $cityId));
    my $extractor = new Ikariam::Extractor(content => $res->content);

    # //div[@id='SatisfactionOverview']//div[@class='value']
    # //div[@id='SatisfactionOverview']//div[@class='text']
    $self->{"cities"}->{$cityId}->{happiness} = $extractor->find('//div[@id="SatisfactionOverview"]//div[@class="value"]');

    # 取 SatisfactionOverview 中的其他值
    # 一個城鎮的市民滿意度結合了多方面的因素
    # check happiness
    # Happiness = Basic bonuses (196 + Capital Bonus + Holiday Bonus(25)) + 
    #             Wine (Tavern Base(12*level) + 
    #             Tavern Bonus(80*step)) + 
    #             Culture (Museum Base(20*level) + 
    #             Cultural Goods Bonus(50*Cultural Goods)) - 
    #             Population (population) - 
    #             Corruption (Corruption rate * population)
    #
    # Growth Rate = Happiness * 0.02

    # Space, 房屋數
    $self->{'cities'}->{$cityId}->{"space"} = {};
    $self->{'cities'}->{$cityId}->{"space"}->{'total'} = $extractor->find('//span[@class="value total"]/text()');
    $self->{'cities'}->{$cityId}->{"space"}->{'occupied'} = $extractor->find('//span[@class="value occupied"]/text()');

    my @values = $extractor->find('//div[@id="CityOverview"]//span[@class="value"]');
    $self->{'cities'}->{$cityId}->{"growth"} = $values[0];
    $self->{'cities'}->{$cityId}->{"incomegold"} = $values[1];

    my $actionPoints = $extractor->find('//div[@id="CityOverview"]//li[@class="actions"]/text()');
    if($actionPoints =~ /(\d+)\/(\d+)/) {
        $self->{'cities'}->{$cityId}->{actionPoints} = $1;
        $self->{'cities'}->{$cityId}->{maxActionPoints} = $2;
    }

    $self->{'cities'}->{$cityId}->{corruption} = $extractor->find('//li[@class="corruption"]//span[@title="目前腐敗程度"]');
    $self->{'cities'}->{$cityId}->{corruption} =~ s/%//g;

    my @citizens_type = qw/citizens woodworkers specialworkers scientists/;
    @values = $extractor->find('//div[@id="PopulationGraph"]//span[@class="count"]');
    foreach my $i (0..$#citizens_type)
    {
        $self->{'cities'}->{$cityId}->{'citizens'}->{$citizens_type[$i]} = $values[$i];
        $self->{'cities'}->{$cityId}->{'citizens'}->{total} += $values[$i];
    }
}

sub checkArmies
{
    my $self = shift;
    my $cityId = shift;
    my %force_types;

    $force_types{'army'} = [ qw/undef undef Slinger Swordsman Phalanx Ram Archer Catapult Gunsman Mortar SteamGiant Gyrocopter Bombardier Doctor Cook/ ];
    $force_types{'fleet'} = [ qw/undef undef Ram-Ship BallistaShip Flamethrower CatapultShip MortarShip PaddleWheelRam DivingBoat/ ];

    foreach my $x (qw/army fleet/) {
        $self->{'cities'}->{$cityId}->{$x} = {};
        my $res = $self->{mech}->get(sprintf('http://%s/index.php?view=cityMilitary-%s&id=%d', $self->{server}, $x, $cityId));
        my @numbers = Ikariam::Extractor->new(content => $res->content)->find('//table//tr/td');
        foreach my $j (0..$#{$force_types{$x}}) {
            next if(!defined($force_types{$x}[$j]));
            if ($numbers[$j] == '-') {
                $self->{'cities'}->{$cityId}->{$x}->{$force_types{$x}[$j]} = 0;
            } else {
                $self->{'cities'}->{$cityId}->{$x}->{$force_types{$x}[$j]} = $numbers[$j];
            }
        }
    }
}

sub checkFriends 
{
    # must check cities first, so we know if we have a museum available.
    my $self = shift;

    foreach my $cityId (keys(%{$self->{'cities'}})) {
        my @locations = @{$self->{'cities'}->{$cityId}->{locations}};
        foreach (0..$#locations) {
            if($locations[$_] eq 'museum') {
                my $res = $self->{mech}->get(sprintf('http://%s/index.php?view=museum&id=%d&position=%d', $self->{server}, $cityId, $_ ));
                my @hrefs = Ikariam::Extractor->new(content => $res->content)->find('//div[@class="content"]/table/tbody/tr/td/a[1]/@href');
                foreach my $href (@hrefs) { 
                    if ($href =~ /&id=(\d+)&/) {
                        $self->{'friends'}->{$1} = undef;
                    }
                }
                last;
            }
        }
    }

    if(-f "friends.txt") {
        # load friends
        open(IN, "friends.txt") or die "Unable to open friends.txt\n";
        while(<IN>) {
            chomp;
            my $friend = Ikariam::User->retrieve(name => $_);
            $self->{friends}->{$friend->id} = undef if(defined($friend));
        }
        close(IN);
    }
}

sub check
{
    my $self = shift;

    # MilitaryAdvisor
    $self->checkMilitaryAdvisorMilitaryMovements();
    $self->checkMilitaryAdvisorCombatReports();
    $self->{'research'} = $self->checkResearch((keys(%{$self->{'cities'}}))[0]);

    # alerts
    # //li[@id="advCities"]/a[@class="normalalert"]
    # //li[@id="advMilitary"]/a[@class="normalalert"]
    # //li[@id="advResearch"]/a[@class="normalalert"]
    # //li[@id="advDiplomacy"]/a[@class="normalalert"]

    # looking for cities
    foreach my $cityId (keys(%{$self->{'cities'}})) {
        $self->changeCity($cityId);
        $self->checkCity($cityId);
        $self->checkTownHall($cityId);
        $self->checkArmies($cityId);
        $self->checkTavern($cityId);
        $self->checkAcademy($cityId);
    }
    # $self->checkFriends();
    return $self->{'cities'};
}

sub getNetIncome {
    my $self = shift;
    my $cityId = shift;

    Carp::croak("must assign cityId") if !defined($cityId);

    # 扣除研發每人花費 8 GOLD
    return ($self->{cities}->{$cityId}->{citizens}->{total}*4 - $self->{cities}->{$cityId}->{citizens}->{scientists}*8 );
}

sub getNavyExpenditure {
    my $self = shift;
    my $cityId = shift;
    my $cities = $self->{cities};

    my $ships = {
        CargoShip => {n => "CargoShip",a => 0,d => 0,s => 4,c => "Steamship",v => 20,A => 0,D => 0},
        'Ram-Ship' => {n => "Ram-Ship",p => 6,w => 56,S => 21,b => "34m",u => 20,m => 1,o => 3,a => 16,d => 16,A => 4,D => 4,s => 10,c => "Sailer",v => 10},
        BallistaShip => {n => "BallistaShip",p => 5,w => 72,S => 29,b => "47m",u => 24,m => 3,o => 5,a => 20,d => 28,A => 5,D => 7,s => 11,c => "Sailer",v => 8,x => "Resistance"},
        Flamethrower => {n => "Flamethrower",p => 5,w => 105,S => 77,b => "1h 55m",u => 45,m => 5,o => 7,a => 40,d => 40,A => 10,D => 10,s => 12,c => "Steamship",v => 8,x => "Assault"},
        CatapultShip => {n => "CatapultShip",p => 10,w => 173,S => 76,b => "3h 11m",u => 57,m => 7,o => 10,a => 60,d => 60,A => 12,D => 12,s => 16,c => "Sailer",v => 6},
        MortarShip => {n => "MortarShip",p => 22,w => 456,S => 282,b => "3h 38m",u => 130,m => 12,o => 15,a => 160,d => 160,A => 35,D => 35,s => 14,c => "Steamship",v => 4},
        PaddleWheelRam => {n => "PaddleWheelRam",i => "steamboat",p => 12,w => 513,S => 167,b => "4h 8m",u => 114,m => 10,o => 13,a => 100,d => 90,A => 20,D => 18,s => 13,c => "Steamship",v => 8,x => "Assault"},
        DivingBoat => {n => "DivingBoat",i => "submarine",p => 16,w => 493,C => 378,b => "5h 5m",u => 126,m => 15,o => 16,a => 110,d => 155,A => 20,D => 30,s => 10,c => "Steamship",v => 2,x => "Resistance"}
    };
    my $cost = 0;
    foreach(keys(%{$cities->{$cityId}->{fleet}})) {
        $cost += $cities->{$cityId}->{fleet}->{$_} * $ships->{$_}->{u};
    }
    return $cost;
}

sub getMilityExpenditure {
    my $self = shift;
    my $cityId = shift;
    my $cities = $self->{cities};
    my $troops = {
        # p 需要人口
        # w 木頭
        # S 硫磺
        # b 製造時間
        # u 維持費用
        # m 最低軍營等級
        # o 最佳軍營等級
        # a 攻擊力
        # d 防守力
        # A 攻擊力加成
        # D 防守力加成
        # s 耐力
        # c 種類
        # v Speed
        # x 攻防加成 Assault, Resistance
        Slinger => {n => "Slinger", p => 1, w => 40, b => "12m", u => 8, m => 1, o => 4, a => 7, d => 6, A => 2, D => 2, s => 10, c => "Human", v => 20}, 
        Swordsman => {n => "Swordsman", p => 2, w => 47, S => 16, b => "17m", u => 16, m => 3, o => 5, a => 18, d => 14, A => 4, D => 3, s => 12, c => "Human", v => 20, x => "Assault"}, 
        Phalanx => {n => "Phalanx", p => 4, w => 104, S => 64, b => "40m", u => 24, m => 4, o => 7, a => 24, d => 40, A => 6, D => 10, s => 14, c => "Human", v => 20, x => "Resistance"}, 
        Ram => {n => "Ram", p => 8, w => 198, S => 128, b => "42m", u => 52, m => 5, o => 8, a => 14, d => 18, A => 3, D => 4, s => 16, c => "Machina", v => 20, x => "Ram"}, 
        Archer => {n => "Archer", p => 4, w => 172, S => 86, b => "49m", u => 32, m => 7, o => 10, a => 40, d => 40, A => 10, D => 10, s => 12, c => "Human", v => 20}, 
        Catapult => {n => "Catapult", p => 10, w => 342, S => 232, b => "49m", u => 72, m => 10, o => 14, a => 36, d => 28, A => 9, D => 7, s => 16, c => "Machina", v => 20, x => "Ram"}, 
        Gunsman => {n => "Gunsman", i => "marksman", p => 7, w => 355, S => 154, b => "1h 23m", u => 58, m => 14, o => 18, a => 80, d => 64, A => 18, D => 14, s => 10, c => "Human", v => 20}, 
        Mortar => {n => "Mortar", p => 12, w => 1325, S => 938, b => "1h 53m", u => 128, m => 19, o => 21, a => 64, d => 64, A => 15, D => 15, s => 16, c => "Machina", v => 20, x => "Ram"}, 
        SteamGiant => {n => "SteamGiant", i => "steamgiant", p => 6, w => 1150, S => 716, b => "1h 45m", u => 68, m => 16, o => 20, a => 100, d => 140, A => 20, D => 30, s => 14, c => "Machina", v => 20, x => "Resistance"}, 
        Gyrocopter => {n => "Gyrocopter", p => 8, w => 1250, S => 670, b => "1h 2m", u => 97, m => 12, o => 16, a => 112, d => 112, A => 25, D => 25, s => 12, c => "Machina", v => 20}, 
        Bombardier => {n => "Bombardier", p => 24, w => 2270, S => 878, b => "2h 10m", u => 228, m => 22, o => 24, a => 200, d => 165, A => 45, D => 35, s => 14, c => "Machina", v => 20, x => "Assault"}, 
        Doctor => {n => "Doctor", i => "medic", p => 6, w => 640, C => 361, b => "1h 2m", u => 244, m => 11, o => 12, a => 4, d => 28, A => 0, D => 0, s => 14, c => "Human", v => 20, x => "Healer"}, 
        Cook => {n => "Cook", p => 4, w => 520, W => 103, b => "38m", u => 138, m => 8, o => 8, a => 6, d => 26, A => 0, D => 0, s => 16, c => "Human", v => 20, x => "Regeneration"}
    };

    my $cost = 0;
    foreach(keys(%{$cities->{$cityId}->{army}})) {
        $cost += $cities->{$cityId}->{army}->{$_} * $troops->{$_}->{u};
    }
    return $cost;
}

sub blanceHurmanResource {
    my $self = shift;
    my $cityId = shift;
    my $workersRatio = {
        'citizens' => 0.4,
        'specialworkers' => 0.3,
        'woodworkers' => 0.7,
    };

    my $netincome = $self->getNetIncome($cityId);

    # --- HR ---
    # 扣除研發,四成種田生產,剩下 3:7 挖資源
    # 四成收入中可用兩成做軍事用途
    # 生產共四成 
    my $produceworkers = int(($netincome * $workersRatio->{citizens}) / 4);

    # 換成生產人力
    my $freePeople = $self->{cities}->{$cityId}->{citizens}->{total} - ($produceworkers + $self->{cities}->{$cityId}->{scientists});

    # XXX
    # 需計算資源開挖上限, 依照島等級區分
    # 木頭
#   create_slider({
#            dir : 'ltr',
#            id : "default",
#            maxValue : 367,
#            overcharge : 0,
#            iniValue : 367,
#            bg : "sliderbg",
#            thumb : "sliderthumb",
#            topConstraint: -10,
#            bottomConstraint: 344,
#            bg_value : "actualValue",
#            bg_overcharge : "overcharge",
#            textfield:"inputWorkers"
#    });

#    	create_slider({
#            dir : 'ltr',
#            id : "default",
#			maxValue : 367,
#			overcharge : 0,
#			iniValue : 367,
#			bg : "sliderbg",
#			thumb : "sliderthumb",
#			topConstraint: -10,
#			bottomConstraint: 344,
#			bg_value : "actualValue",
#			bg_overcharge : "overcharge",
#			textfield:"inputWorkers"
#	});

    Carp::carp(sprintf("Suggested HR palnning - produce: %s wood %s special %s\n", $produceworkers, int($freePeople*0.3), int($freePeople*0.7)));
}

sub logout
{
    my $self = shift;
    $self->{mech}->get(sprintf('http://%s/index.php?action=loginAvatar&function=logout', $self->{server}));
}

sub login
{
    my $self = shift;

    my $res = $self->{mech}->post(sprintf("http://%s/index.php?action=loginAvatar&function=login", $self->{server}), [
        name => $self->{user},
        password => $self->{pass},
        ]);

    my @cities = Ikariam::Extractor->new(content => $res->content)->find('//option[@class="avatarCities coords"]/@value');

    if($#cities<0) {
        die ("login failed\n");
    }
    foreach (@cities) {
        $self->{'cities'}->{$_} = {};
        $self->{'cities'}->{$_}->{id} = $_;
        if(-f "city-$_-dump.yaml") {
            $self->{'cities'}->{$_} = LoadFile("city-$_-dump.yaml");
        }
    }
}


1;