diff --git a/lib/Lacuna.pm b/lib/Lacuna.pm index 5cb59514..755bd99b 100644 --- a/lib/Lacuna.pm +++ b/lib/Lacuna.pm @@ -7,7 +7,7 @@ use Config::JSON; useall __PACKAGE__; -our $VERSION = 3.0829; +our $VERSION = 3.0830; my $config = Config::JSON->new('/data/Lacuna-Server/etc/lacuna.conf'); my $db = Lacuna::DB->connect($config->get('db/dsn'),$config->get('db/username'),$config->get('db/password'), { mysql_enable_utf8 => 1}); diff --git a/lib/Lacuna/Role/Ship/Arrive/TriggerDefense.pm b/lib/Lacuna/Role/Ship/Arrive/TriggerDefense.pm index ec311e0d..ba8cd4d0 100644 --- a/lib/Lacuna/Role/Ship/Arrive/TriggerDefense.pm +++ b/lib/Lacuna/Role/Ship/Arrive/TriggerDefense.pm @@ -310,6 +310,7 @@ sub system_saw_combat { my $attacked_combat = 0; ( $attacked_saws, $attacked_combat ) = $self->saw_stats($attacked_body) if ($attacked_body->isa('Lacuna::DB::Result::Map::Body::Planet')); +# Attacked planet defense a bit more fierce. $total_combat += 2 * $attacked_combat; while (my $defending_body = $defending_bodies->next) { # asteroids don't have SAWs @@ -357,11 +358,10 @@ sub saw_stats { $cnt++; next if $saw->level < 1; next if $saw->efficiency < 1; - $planet_combat += int( (150 * $saw->level * ($saw->level+1) * $saw->efficiency)/100 + 0.5); + $planet_combat += int( (5 * ($saw->level + 1) * ($saw->level+1) * $saw->efficiency)/2 + 0.5); push @saws, $saw; last if $cnt >= 10; } -# $planet_combat = $planet_combat * $cnt; return \@saws, $planet_combat; } @@ -377,16 +377,16 @@ sub saw_combat { } else { my $perc_1 = int( ($self->combat * 100)/$saw_combat + 0.5); - my $perc_2 = int( $self->combat/ - ((300 * $saw->level * ($saw->level+1) * $saw->efficiency)/100)); - $perc_2 = 5 if $perc_2 > 5; - my $perc = $perc_1 > $perc_2 ? $perc_1 : $perc_2; - $perc = $perc == 1 ? 1 : int($perc * $saw->efficiency/100 +0.5); - if ($perc < 1) { - if (randint(0,99) < 15) { $perc = 1; } else { $perc = 0; } + my $perc_2 = int( $self->combat * 100/ + (5 * ($saw->level + 1) * ($saw->level+1) * $saw->efficiency)); + $perc_2 = 100 if $perc_2 > 99; + if ($perc_1 < 1) { + $perc_2 = $perc_2 > 1 ? $perc_2 : 1; + if (randint(0,99) < $perc_2) { $perc_1 = 1; } else { $perc_1 = 0; } } + $perc_1 = $perc_1 == 1 ? 1 : int($perc_1 * $saw->efficiency/100 +0.5); # printf "%3d\n", $perc; - $saw->spend_efficiency($perc); + $saw->spend_efficiency($perc_1); } unless ($saw->is_working) { $saw->start_work({}, 60 * 15); diff --git a/var/www/public/changes.txt b/var/www/public/changes.txt index f851beea..d0073b78 100644 --- a/var/www/public/changes.txt +++ b/var/www/public/changes.txt @@ -1,3 +1,6 @@ +3.0830: + - Mod: Even more changes to SAWs. (We'll keep banging on these keys.) + 3.0829: - Add: Added bulk trading in plans/glyphs/ships to SST -- 2.51.2 From 73176e072a4eebb93b5a4ff8ffaefcb9954cd575 Mon Sep 17 00:00:00 2001 From: ian docherty Date: Thu, 29 Dec 2011 15:55:56 -0500 Subject: [PATCH 02/75] Attempt to fix the 1 in a 1000 ship builds that go astray --- lib/Lacuna/RPC/Building/Shipyard.pm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/Lacuna/RPC/Building/Shipyard.pm b/lib/Lacuna/RPC/Building/Shipyard.pm index bc6892ae..bd213c8b 100644 --- a/lib/Lacuna/RPC/Building/Shipyard.pm +++ b/lib/Lacuna/RPC/Building/Shipyard.pm @@ -85,8 +85,10 @@ sub build_ship { confess [1001, "Quantity must be a positive integer"]; } my $costs; - my $empire = $self->get_empire_by_session($session_id); - my $building = $self->get_building($empire, $building_id); + my $empire = $self->get_empire_by_session($session_id); + my $building = $self->get_building($empire, $building_id); + my $body_id = $building->body_id; + for (1..$quantity) { my $ship = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->new({type => $type}); if (not defined $costs) { @@ -95,6 +97,8 @@ sub build_ship { } $building->spend_resources_to_build_ship($costs); $building->build_ship($ship, $costs->{seconds}); + $ship->body_id($body_id); + $ship->update; } return $self->view_build_queue($empire, $building); } -- 2.51.2 From b421fcb0423c5cb916471ccacc19c30839d94e72 Mon Sep 17 00:00:00 2001 From: lemming552 Date: Thu, 29 Dec 2011 22:34:09 -0700 Subject: [PATCH 03/75] Grammar modification. --- lib/Lacuna/DB/Result/Propositions/RenameStar.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Lacuna/DB/Result/Propositions/RenameStar.pm b/lib/Lacuna/DB/Result/Propositions/RenameStar.pm index 84458e79..659e2040 100644 --- a/lib/Lacuna/DB/Result/Propositions/RenameStar.pm +++ b/lib/Lacuna/DB/Result/Propositions/RenameStar.pm @@ -11,7 +11,7 @@ before pass => sub { my $star = $station->stars->find($self->scratch->{star_id}); my $name = $self->scratch->{name}; if (Lacuna->db->resultset('Lacuna::DB::Result::Map::Star')->search({name=>$name, 'id'=>{'!='=>$star->id}})->count) { - $self->pass_extra_message('Unfortunately, by the time the proposition passed, the name *'.$name.'* had already taken, effectively nullifying the vote.'); + $self->pass_extra_message('Unfortunately, by the time the proposition passed, the name *'.$name.'* had already been taken, effectively nullifying the vote.'); } elsif ($star->station_id != $station->id) { $self->pass_extra_message('Unfortunately, by the time the proposition passed, the star was no longer under the jurisdiction of this station, effectively nullifying the vote.'); -- 2.51.2 From ffc2beabd8ffd74db5fcc652641af04e2efd9564 Mon Sep 17 00:00:00 2001 From: lemming552 Date: Fri, 30 Dec 2011 07:22:54 -0700 Subject: [PATCH 04/75] Check to make sure spy is on planet for training. --- lib/Lacuna/DB/Result/Building/IntelTraining.pm | 3 +++ lib/Lacuna/DB/Result/Building/MayhemTraining.pm | 6 ++++++ lib/Lacuna/DB/Result/Building/PoliticsTraining.pm | 3 +++ lib/Lacuna/DB/Result/Building/TheftTraining.pm | 3 +++ 4 files changed, 15 insertions(+) diff --git a/lib/Lacuna/DB/Result/Building/IntelTraining.pm b/lib/Lacuna/DB/Result/Building/IntelTraining.pm index bca6dc55..dd6180e1 100644 --- a/lib/Lacuna/DB/Result/Building/IntelTraining.pm +++ b/lib/Lacuna/DB/Result/Building/IntelTraining.pm @@ -65,6 +65,9 @@ sub get_spy { if ($spy->empire_id ne $self->body->empire_id) { confess [1013, "You don't control that spy."]; } + if ($spy->on_body_id != $self->body->id) { + confess [1013, "Spy must be on planet to train."]; + } return $spy; } diff --git a/lib/Lacuna/DB/Result/Building/MayhemTraining.pm b/lib/Lacuna/DB/Result/Building/MayhemTraining.pm index 599bb5e7..2455933b 100644 --- a/lib/Lacuna/DB/Result/Building/MayhemTraining.pm +++ b/lib/Lacuna/DB/Result/Building/MayhemTraining.pm @@ -65,6 +65,12 @@ sub get_spy { if ($spy->empire_id ne $self->body->empire_id) { confess [1013, "You don't control that spy."]; } + if ($spy->on_body_id != $self->body->id) { + confess [1013, "Spy must be on planet to train."]; + } + if ($spy->on_body_id != $self->body->id) { + confess [1013, "Spy must be on planet to train."]; + } return $spy; } diff --git a/lib/Lacuna/DB/Result/Building/PoliticsTraining.pm b/lib/Lacuna/DB/Result/Building/PoliticsTraining.pm index d8ec7fd5..02c16882 100644 --- a/lib/Lacuna/DB/Result/Building/PoliticsTraining.pm +++ b/lib/Lacuna/DB/Result/Building/PoliticsTraining.pm @@ -65,6 +65,9 @@ sub get_spy { if ($spy->empire_id ne $self->body->empire_id) { confess [1013, "You don't control that spy."]; } + if ($spy->on_body_id != $self->body->id) { + confess [1013, "Spy must be on planet to train."]; + } return $spy; } diff --git a/lib/Lacuna/DB/Result/Building/TheftTraining.pm b/lib/Lacuna/DB/Result/Building/TheftTraining.pm index 862c4742..f2a37f7e 100644 --- a/lib/Lacuna/DB/Result/Building/TheftTraining.pm +++ b/lib/Lacuna/DB/Result/Building/TheftTraining.pm @@ -65,6 +65,9 @@ sub get_spy { if ($spy->empire_id ne $self->body->empire_id) { confess [1013, "You don't control that spy."]; } + if ($spy->on_body_id != $self->body->id) { + confess [1013, "Spy must be on planet to train."]; + } return $spy; } -- 2.51.2 From 7f7487189c0cdb869e653b472ea9873ea1050e0c Mon Sep 17 00:00:00 2001 From: lemming552 Date: Fri, 30 Dec 2011 07:24:48 -0700 Subject: [PATCH 05/75] Changes... --- var/www/public/changes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/var/www/public/changes.txt b/var/www/public/changes.txt index d0073b78..643f90c9 100644 --- a/var/www/public/changes.txt +++ b/var/www/public/changes.txt @@ -1,5 +1,6 @@ 3.0830: - Mod: Even more changes to SAWs. (We'll keep banging on these keys.) + - Fix: Correspondance school is no longer an acredited option for spies. 3.0829: - Add: Added bulk trading in plans/glyphs/ships to SST -- 2.51.2 From 6e04fe5223b371fabe8cdd278ad8a8b3fbea2642 Mon Sep 17 00:00:00 2001 From: ian docherty Date: Fri, 30 Dec 2011 19:08:21 -0500 Subject: [PATCH 06/75] Added admin pages to administer DeLamberti attacks --- lib/Lacuna/Web/Admin.pm | 91 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/lib/Lacuna/Web/Admin.pm b/lib/Lacuna/Web/Admin.pm index 1dda7f89..c37e1ca9 100644 --- a/lib/Lacuna/Web/Admin.pm +++ b/lib/Lacuna/Web/Admin.pm @@ -10,7 +10,7 @@ use Module::Find; use UUID::Tiny ':std'; use Lacuna::Util qw(format_date); use List::Util qw(sum); - +use Data::Dumper; sub www_send_test_message { my ($self, $request, $id) = @_; @@ -1211,6 +1211,9 @@ sub www_delambert { $out .= 'Maximum sell glyph trades in any one zone'; $out .= 'Maximum sell plan trades in any one zone'; $out .= ' '; + $out .= '

War Status

'; + + $out .= '

DeLamberti Colonies

'; $out .= ''; while (my $body = $bodies->next) { @@ -1220,9 +1223,95 @@ sub www_delambert { return $self->wrap($out); } +sub www_delambert_war { + my ($self, $request) = @_; + my ($scratch) = Lacuna->db->resultset('Lacuna::DB::Result::AIScratchPad')->search({ai_empire_id => -9, body_id => 0}); + my $scratchpad = $scratch->pad; + if ($request->param('action')) { + $scratchpad->{attack}{$request->param('attacker_id')} = { + status => $request->param('action') eq 'Hold' ? 'Holding' : 'Attacking', + sweepers => $request->param('sweepers'), + scows => $request->param('scows'), + snarks => $request->param('snarks'), + colony_id => $request->param('colony_id'), + }; + $scratch->pad($scratchpad); + $scratch->update; + } + my $out = ''; + $out .= "

DeLamberti war status

\n"; + my @ai_defence = Lacuna->db->resultset('Lacuna::DB::Result::AIBattleSummary')->search({ + defending_empire_id => -9, + }); + my @ai_attack = Lacuna->db->resultset('Lacuna::DB::Result::AIBattleSummary')->search({ + attacking_empire_id => -9, + }); + # If the AI is attacked, we don't care who won or lost, just that there was an action against the AI + my %defence = map { + $_->attacking_empire_id => { + attack_victories => $_->attack_victories, + defense_victories => $_->defense_victories, + attack_spy_hours => $_->attack_spy_hours, + weight => $_->attack_victories + $_->defense_victories + $_->attack_spy_hours * 2, + } + } @ai_defence; + + # If the AI attacks, we just care about when the AI wins the attack + my %attack = map { $_->defending_empire_id => $_->attack_victories + $_->attack_spy_hours * 2 } @ai_defence; + + # Sort the attackers so that those who have done the most un-retaliated damage are shown first + my @worst_attackers = sort {( $defence{$b}{weight} - defined $attack{$b} ? $attack{$b} : 0) <=> ( $defence{$a}{weight} - defined $attack{$a} ? $attack{$a} : 0 ) } keys %defence; + + $out .= "
IdNameXYZone
\n"; +ATTACKER: + foreach my $attacker (@worst_attackers) { + my $attack_empire = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->find($attacker); + next ATTACKER unless $attack_empire; + + # Obtain all colonies of the attacking empire, sorted by population desc. + my @colonies = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->search({ + empire_id => $attacker, + }); + @colonies = sort {$b->population <=> $a->population} @colonies; + + if (not defined $scratchpad->{attack}{$attacker}) { + $scratchpad->{attack}{$attacker} = { + status => 'Holding', + colony_id => $colonies[0]->id, + sweepers => 1000, + snarks => 200, + scows => 200, + }; + $scratch->pad($scratchpad); + $scratch->update; + } + + my $status = $scratchpad->{attack}{$attacker}{status}; + my $sweepers = $scratchpad->{attack}{$attacker}{sweepers}; + my $snarks = $scratchpad->{attack}{$attacker}{snarks}; + my $scows = $scratchpad->{attack}{$attacker}{scows}; + my $action = $status eq 'Holding' ? 'Attack' : 'Hold'; + my $counter_attacks = defined $attack{$attacker} ? $attack{$attacker} : 0; + $out .= ""; + $out .= ""; + $out .= ""; + $out .= ""; + $out .= ""; + $out .= ""; + $out .= ""; + $out .= ""; + } + $out .= "
AttackerVictoriesDefeatsSpy HoursCounter AttacksWeightStatusColonyAttack SweepersAttack ScowsAttack SnarkAction
".$attack_empire->name."".$defence{$attacker}{attack_victories}."".$defence{$attacker}{defense_victories}."".$defence{$attacker}{attack_spy_hours}."$counter_attacks".$defence{$attacker}{weight}."$status
\n"; + return $self->wrap($out); +} sub wrap { my ($self, $content) = @_; -- 2.51.2 From 213930574d44eaad4844226f2368cce706c7b396 Mon Sep 17 00:00:00 2001 From: ian docherty Date: Sat, 31 Dec 2011 19:35:40 -0500 Subject: [PATCH 07/75] Added to the war capability. --- lib/Lacuna/AI/DeLambert.pm | 139 +++++++++++++++++++++--- lib/Lacuna/DB/Result/Map/Body/Planet.pm | 13 ++- lib/Lacuna/DB/Result/Ships.pm | 4 +- lib/Lacuna/RPC/Building/SpacePort.pm | 2 + 4 files changed, 139 insertions(+), 19 deletions(-) diff --git a/lib/Lacuna/AI/DeLambert.pm b/lib/Lacuna/AI/DeLambert.pm index 6d0ac00b..ffb5630b 100644 --- a/lib/Lacuna/AI/DeLambert.pm +++ b/lib/Lacuna/AI/DeLambert.pm @@ -105,23 +105,24 @@ sub ship_building_priorities { sub run_hourly_colony_updates { my ($self, $colony) = @_; - $self->demolish_bleeders($colony); - $self->set_defenders($colony); - $self->pod_check($colony, 20); - $self->repair_buildings($colony); - $self->train_spies($colony, 1); - $self->build_ships_max($colony); - $self->run_missions($colony); - $self->buy_trade($colony); - $self->sell_glyph_trade($colony); - $self->sell_plan_trade($colony); - $self->check_enemy_spy_action($colony); +# $self->demolish_bleeders($colony); +# $self->set_defenders($colony); +# $self->pod_check($colony, 20); +# $self->repair_buildings($colony); +# $self->train_spies($colony, 1); +# $self->build_ships_max($colony); +# $self->run_missions($colony); +# $self->buy_trade($colony); +# $self->sell_glyph_trade($colony); +# $self->sell_plan_trade($colony); +# $self->check_enemy_spy_action($colony); } sub run_hourly_empire_updates { my ($self, $empire) = @_; $self->process_email($empire); + $self->retaliate($empire); } sub get_colony_scratchpad { @@ -410,21 +411,129 @@ sub buy_trade { } +sub retaliate { + my ($self) = @_; + + say "#### Retaliate! ####"; + my $empire = $self->empire; + $self->scratch->discard_changes; + my $attack = $self->scratch->pad->{attack}; + + my @del_colonies = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->search({ + empire_id => -9, + }); + +TARGET: + foreach my $target_id (keys %$attack) { + my $target = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->find($target_id); + if ($target) { + say " Target empire '".$target->name."' status '".$attack->{$target_id}{status}."'"; + if ($attack->{$target_id}{status} eq "Attacking") { + my $target_colony_id = $attack->{$target_id}{colony_id}; + my $num_sweepers = $attack->{$target_id}{sweepers}; + my $num_scows = $attack->{$target_id}{scows}; + my $num_snarks = $attack->{$target_id}{snarks}; + my $target_colony = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->find($target_colony_id); + next TARGET unless $target_colony; + next TARGET if $target_colony->empire_id != $target_id; + say " Attack '".$target_colony->name."' with $num_sweepers sweepers, $num_scows scows, $num_snarks snarks"; + + # Sort the DeLamberti colonies, closest to the target first + @del_colonies = sort {$self->distance_comp($a,$b,$target_colony)} @del_colonies; + # Get a percentage of ships from the closest colonies. + my @sweepers; + my @scows; + my @snarks; + foreach my $del_colony (@del_colonies) { + if (@sweepers < $num_sweepers) { + my @ships = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->search({ + type => 'sweeper', + task => 'Docked', + body_id => $del_colony->id, + }); + # 20% of sweepers + my $quantity = int(@ships / 5); + say " Colony has ".scalar(@ships)." sweepers"; + if (@sweepers + $quantity > $num_sweepers) { + $quantity = $num_sweepers - @sweepers; + } + say " taking $quantity sweepers from ".$del_colony->name; + @sweepers = (@sweepers, splice(@ships, 0, $quantity)); + say " Now got ".scalar(@sweepers)." sweepers"; + } + if (@scows < $num_scows) { + my @ships = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->search({ + type => 'scow', + task => 'Docked', + body_id => $del_colony->id, + }); + # 50% of scows + my $quantity = int(@ships / 2); + if (@scows + $quantity > $num_scows) { + $quantity = $num_scows - @scows; + } + say " taking $quantity scows from ".$del_colony->name; + @scows = (@scows, splice(@ships, 0, $quantity)); + } + if (@snarks < $num_snarks) { + my @ships = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->search({ + type => {like => "snark%"}, + task => 'Docked', + body_id => $del_colony->id, + }); + # 50% of snarks + my $quantity = int(@ships / 2); + if (@snarks + $quantity > $num_snarks) { + $quantity = $num_snarks - @snarks; + } + say " taking $quantity snarks from ".$del_colony->name; + @snarks = (@snarks, splice(@ships, 0, $quantity)); + } + } + # Send all the ships and adjust their travel time to the latest arrival + my $arrival_time; + for my $ship (@snarks,@sweepers,@scows) { + say " sending ship ID".$ship->id; + $ship->send(target => $target_colony); + if (not defined $arrival_time or $ship->date_available > $arrival_time) { + $arrival_time = $ship->date_available; + } + } + say " Latest arrival time is $arrival_time"; + for my $ship (@sweepers,@snarks,@scows) { + $ship->date_available($arrival_time); + $ship->update; + } + } + } + } +} + +sub distance_comp { + my ($self, $left, $right, $target) = @_; + + my $distance_left = ($left->x - $target->x) * ($left->x - $target->x) + ($left->y - $target->y) * ($left->y - $target->y); + my $distance_right = ($right->x - $target->x) * ($right->x - $target->x) + ($right->y - $target->y) * ($right->y - $target->y); + return $distance_left <=> $distance_right; +} + sub process_email { my ($self) = @_; my $empire = $self->empire; my $messages = $self->empire->received_messages->search({ has_read => 0, - tag => 'Correspondence', +# tag => 'Correspondence', }); MESSAGE: while (my $message = $messages->next) { print("Received message [".$message->subject."]\n"); if ($message->from_id == $self->empire->id) { - # message is from oneself - $message->has_read(1); - $message->update; + if ($message->subject eq "Trade Withdrawn") { + $message->has_read(1); + $message->has_trashed(1); + $message->update; + } next MESSAGE; } my $request_empire = Lacuna::db->resultset('Lacuna::DB::Result::Empire')->find($message->from_id); diff --git a/lib/Lacuna/DB/Result/Map/Body/Planet.pm b/lib/Lacuna/DB/Result/Map/Body/Planet.pm index 3c835092..c9e626a9 100644 --- a/lib/Lacuna/DB/Result/Map/Body/Planet.pm +++ b/lib/Lacuna/DB/Result/Map/Body/Planet.pm @@ -190,10 +190,12 @@ around get_status => sub { ); my @colonies; my @allies; + my $foreign_bodies; while (my $ship = $incoming_ships->next) { if ($ship->date_available->epoch <= $now) { - $ship->foreign_body($self); - $ship->body->tick; +# $ship->foreign_body($self); +# $ship->body->tick; + $foreign_bodies->{$ship->body_id} = 1; } else { unless (scalar @colonies) { # we don't look it up unless we have incoming @@ -213,6 +215,13 @@ around get_status => sub { }; } } + # tick all bodies where ships have arrived + foreach my $body_id (keys %$foreign_bodies) { + my $body = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->find($body_id); + if ($body) { + $body->tick; + } + } } $out->{needs_surface_refresh} = $self->needs_surface_refresh; if ($self->needs_surface_refresh) { diff --git a/lib/Lacuna/DB/Result/Ships.pm b/lib/Lacuna/DB/Result/Ships.pm index 18b54981..fb8dc7b6 100644 --- a/lib/Lacuna/DB/Result/Ships.pm +++ b/lib/Lacuna/DB/Result/Ships.pm @@ -236,8 +236,8 @@ sub get_status { type => 'body', }; my $orbiting = { - id => $self->foreign_body_id, - name => $self->foreign_body->name, + id => $self->foreign_body_id, + name => $self->foreign_body->name, type => 'body', x => $self->foreign_body->x, y => $self->foreign_body->y, diff --git a/lib/Lacuna/RPC/Building/SpacePort.pm b/lib/Lacuna/RPC/Building/SpacePort.pm index 12bbeece..ed6e06ab 100644 --- a/lib/Lacuna/RPC/Building/SpacePort.pm +++ b/lib/Lacuna/RPC/Building/SpacePort.pm @@ -632,6 +632,8 @@ sub view_foreign_ships { my @my_planets = $empire->planets->get_column('id')->all; while (my $ship = $ships->next) { if ($ship->date_available->epoch <= $now) { + # it might be more efficient to move this out of the loop and just remember those bodies + # that need to be ticked. Rather than ticking the same body (potentially) many times $ship->body->tick; } else { -- 2.51.2 From 9cad3ca4643ba05da865158f1821bfc77a93162c Mon Sep 17 00:00:00 2001 From: ian docherty Date: Sat, 31 Dec 2011 20:05:28 -0500 Subject: [PATCH 08/75] DeLamberti declaration of war --- bin/delambert/declare_war.pl | 80 ++++++++++++++++++++++++++++++++++++ lib/Lacuna/AI/DeLambert.pm | 33 +++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 bin/delambert/declare_war.pl diff --git a/bin/delambert/declare_war.pl b/bin/delambert/declare_war.pl new file mode 100644 index 00000000..242fcf1b --- /dev/null +++ b/bin/delambert/declare_war.pl @@ -0,0 +1,80 @@ +use 5.010; +use strict; +use lib '/data/Lacuna-Server/lib'; +use Lacuna::DB; +use Lacuna; +use Lacuna::Util qw(randint format_date); +use Lacuna::AI::DeLambert; +use Getopt::Long; +use List::MoreUtils qw(uniq); +use utf8; +$|=1; +our $quiet; +our $to_all; +our $to_alliance; +our $to_empire; +our $attackers; +GetOptions( + 'quiet' => \$quiet, + 'all' => \$to_all, + 'alliance=s' => \$to_alliance, + 'empire=s' => \$to_empire, + 'attackers=s' => \$attackers, +); + +out('Started'); +my $start = time; + +if (not $attackers) { + out('ERROR: Must specify attackers'); + exit; +} + +out('Loading DB'); +our $db = Lacuna->db; +my $empires = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->search({ +}); + +out('getting empires...'); +my $de_lambert = Lacuna::AI::DeLambert->new; + +out("Sending attack message about '$attackers'"); + +while (my $empire = $empires->next) { + my $send_email = 0; + if ($to_all) { + $send_email = 1; + } + if ($empire->name eq 'icd' or $empire->name eq 'icydee' or $empire->name eq 'Sweden' or $empire->name eq 'Norway') { + $send_email = 1; + } + if ($to_empire and $to_empire eq $empire->name) { + $send_email = 1; + } + if ($to_alliance and $empire->alliance and $empire->alliance->name eq $to_alliance) { + $send_email = 1; + } + if ($send_email) { + out("Email to ".$empire->name); + + $de_lambert->attack_email($empire, $attackers); + } +} + + +my $finish = time; +out('Finished'); +out((($finish - $start)/60)." minutes have elapsed"); + +############### +## SUBROUTINES +############### + +sub out { + my $message = shift; + unless ($quiet) { + say format_date(DateTime->now), " ", $message; + } +} + + diff --git a/lib/Lacuna/AI/DeLambert.pm b/lib/Lacuna/AI/DeLambert.pm index 6d0ac00b..c5c5fa28 100644 --- a/lib/Lacuna/AI/DeLambert.pm +++ b/lib/Lacuna/AI/DeLambert.pm @@ -531,6 +531,39 @@ sub process_email { } } +sub attack_email { + my ($self, $empire, $attackers) = @_; + + my $message = qq{ +We are the DeLamberti. + +It is with great sadness and anger that we have to inform you that we have recently been attacked +by $attackers with great loss of life at one of our trading posts. + +We believed that The Lacuna Expanse was a peaceful place where we could trade to mutual benefit +but this was not meant to be. + +While we are by nature a peaceful species, we will not stand by and let this unprovoked attack go +without a response. + +We proclaim that from this day the DeLamberti are at war with $attackers. We will need time to +decide on an appropriate level of response, but be warned, respond we will! + +To all other peaceful empires, understand that we will continue to trade with you, but any attacks +against our trading posts will be met with appropriate force. + +Guilliame de Lambert 9th +}; + $empire->send_message( + tag => 'Correspondence', + subject => 'Declaration of war', + from => $self->empire, + body => $message, + ); + +} + + sub unsolicited_email { my ($self, $empire) = @_; -- 2.51.2 From ff8adef52e58cb9a8604bfb3339af9c589c78516 Mon Sep 17 00:00:00 2001 From: lemming552 Date: Sun, 1 Jan 2012 00:31:23 -0700 Subject: [PATCH 09/75] Alliance Mapping data made available. --- bin/summarize_server.pl | 394 +++++++++++++++++++---------- lib/Lacuna.pm | 2 +- lib/Lacuna/DB/Result/Log/Colony.pm | 4 + var/upgrades/3.0831.sql | 4 + var/www/public/changes.txt | 3 + 5 files changed, 270 insertions(+), 137 deletions(-) create mode 100644 var/upgrades/3.0831.sql diff --git a/bin/summarize_server.pl b/bin/summarize_server.pl index 5d580f4d..8855c1e1 100644 --- a/bin/summarize_server.pl +++ b/bin/summarize_server.pl @@ -10,34 +10,36 @@ use SOAP::Amazon::S3; use Lacuna::Constants qw(SHIP_TYPES); -$|=1; + $|=1; -our $quiet; + our $quiet; -GetOptions( + GetOptions( 'quiet' => \$quiet, -); + ); -out('Started'); -my $start = DateTime->now; + out('Started'); + my $start = DateTime->now; -out('Loading DB'); -our $db = Lacuna->db; + out('Loading DB'); + our $db = Lacuna->db; -summarize_spies(); -summarize_colonies(); -summarize_empires(); -summarize_alliances(); -delete_old_records($start); -rank_spies(); -rank_colonies(); -rank_empires(); -rank_alliances(); -generate_overview(); + summarize_spies(); + summarize_colonies(); + my $mapping = summarize_empires(); + summarize_alliances(); + delete_old_records($start); + rank_spies(); + rank_colonies(); + rank_empires(); + rank_alliances(); + output_map($mapping); + generate_overview(); -my $finish = time; -out('Finished'); -out((($finish - $start->epoch)/60)." minutes have elapsed"); + my $finish = time; + out('Finished'); + out((($finish - $start->epoch)/60)." minutes have elapsed"); +exit; ############### @@ -112,7 +114,7 @@ sub generate_overview { asteroids => 'Lacuna::DB::Result::Map::Body::Asteroid%', ); foreach my $key (keys %body_types) { - out($key); + out($key); my $type = $bodies->search({class => {like => $body_types{$key}}}); $out{bodies}{types}{$key} = { count => $type->count, @@ -126,7 +128,7 @@ sub generate_overview { # flesh out orbits out('Flesh Out Orbital Stats'); foreach my $orbit (1..8) { - out($orbit); + out($orbit); $out{orbits}{$orbit} = { inhabited => $bodies->search({empire_id => {'>', 0}, orbit => $orbit})->count, bodies => $bodies->search({orbit => $orbit})->count, @@ -137,7 +139,7 @@ sub generate_overview { out('Flesh Out Building Stats'); my $distinct = $buildings->search(undef, { group_by => ['class'] })->get_column('class'); while (my $class = $distinct->next) { - out($class); + out($class); my $type_rs = $buildings->search({class=>$class}); my $count = $type_rs->count; $out{buildings}{types}{$class->name} = { @@ -230,122 +232,192 @@ sub delete_old_records { } sub summarize_alliances { - out('Summarizing Alliances'); - my $logs = $db->resultset('Lacuna::DB::Result::Log::Alliance'); - my $alliances = $db->resultset('Lacuna::DB::Result::Alliance'); - my $empire_logs = $db->resultset('Lacuna::DB::Result::Log::Empire'); - while (my $alliance = $alliances->next) { - next if ! defined $alliance->leader_id; # until Alliances get deleted... - out($alliance->name); - my %alliance_data = ( - date_stamp => DateTime->now, - space_station_count => 0, - influence => 0, - alliance_id => $alliance->id, - alliance_name => $alliance->name, - ); - my $empires = $empire_logs->search({alliance_id => $alliance->id}); - while ( my $empire = $empires->next) { - $alliance_data{member_count}++; - $alliance_data{colony_count} += $empire->colony_count; - $alliance_data{space_station_count} += $empire->space_station_count; - $alliance_data{influence} += $empire->influence; - $alliance_data{population} += $empire->population; - $alliance_data{building_count} += $empire->building_count; - $alliance_data{average_building_level} += $empire->average_building_level; - $alliance_data{defense_success_rate} += $empire->defense_success_rate; - $alliance_data{offense_success_rate} += $empire->offense_success_rate; - $alliance_data{dirtiest} += $empire->dirtiest; - $alliance_data{spy_count} += $empire->spy_count; - $alliance_data{average_empire_size} += $empire->empire_size; - $alliance_data{average_university_level} += $empire->university_level; - } - if ($alliance_data{member_count}) { - $alliance_data{average_empire_size} /= $alliance_data{member_count}; - $alliance_data{average_university_level}/= $alliance_data{member_count}; - $alliance_data{average_building_level} /= $alliance_data{member_count}; - $alliance_data{offense_success_rate} /= $alliance_data{member_count}; - $alliance_data{defense_success_rate} /= $alliance_data{member_count}; - } - my $log = $logs->search({alliance_id => $alliance->id},{rows=>1})->single; - if (defined $log) { - if ($alliance_data{member_count}) { - $log->update(\%alliance_data); - } - else { - $log->delete; - } - } - else { - $logs->new(\%alliance_data)->insert; - } - } + out('Summarizing Alliances'); + my $logs = $db->resultset('Lacuna::DB::Result::Log::Alliance'); + my $alliances = $db->resultset('Lacuna::DB::Result::Alliance'); + my $empire_logs = $db->resultset('Lacuna::DB::Result::Log::Empire'); + while (my $alliance = $alliances->next) { + next if ! defined $alliance->leader_id; # until Alliances get deleted... + out($alliance->name); + my %alliance_data = ( + date_stamp => DateTime->now, + space_station_count => 0, + influence => 0, + alliance_id => $alliance->id, + alliance_name => $alliance->name, + ); + my $empires = $empire_logs->search({alliance_id => $alliance->id}); + while ( my $empire = $empires->next) { + $alliance_data{member_count}++; + $alliance_data{colony_count} += $empire->colony_count; + $alliance_data{space_station_count} += $empire->space_station_count; + $alliance_data{influence} += $empire->influence; + $alliance_data{population} += $empire->population; + $alliance_data{building_count} += $empire->building_count; + $alliance_data{average_building_level} += $empire->average_building_level; + $alliance_data{defense_success_rate} += $empire->defense_success_rate; + $alliance_data{offense_success_rate} += $empire->offense_success_rate; + $alliance_data{dirtiest} += $empire->dirtiest; + $alliance_data{spy_count} += $empire->spy_count; + $alliance_data{average_empire_size} += $empire->empire_size; + $alliance_data{average_university_level} += $empire->university_level; + } + if ($alliance_data{member_count}) { + $alliance_data{average_empire_size} /= $alliance_data{member_count}; + $alliance_data{average_university_level}/= $alliance_data{member_count}; + $alliance_data{average_building_level} /= $alliance_data{member_count}; + $alliance_data{offense_success_rate} /= $alliance_data{member_count}; + $alliance_data{defense_success_rate} /= $alliance_data{member_count}; + } + my $log = $logs->search({alliance_id => $alliance->id},{rows=>1})->single; + if (defined $log) { + if ($alliance_data{member_count}) { + $log->update(\%alliance_data); + } + else { + $log->delete; + } + } + else { + $logs->new(\%alliance_data)->insert; + } + } } sub summarize_empires { - out('Summarizing Empires'); - my $logs = $db->resultset('Lacuna::DB::Result::Log::Empire'); - my $empires = $db->resultset('Lacuna::DB::Result::Empire')->search({ id => {'>' => 1} }); - my $colony_logs = $db->resultset('Lacuna::DB::Result::Log::Colony'); - while (my $empire = $empires->next) { - out($empire->name); - my %empire_data = ( - date_stamp => DateTime->now, - university_level => $empire->university_level, - empire_id => $empire->id, - empire_name => $empire->name, - alliance_id => $empire->alliance_id, - ); - if ($empire->alliance_id) { - $empire_data{alliance_name} = $empire->alliance->name; - } - else { - $empire_data{alliance_name} = undef; - } - my $colonies = $colony_logs->search({empire_id => $empire->id}); - while ( my $colony = $colonies->next) { - if ($colony->is_space_station) { - $empire_data{influence} += $colony->influence; - $empire_data{space_station_count}++; - } - else { - $empire_data{colony_count}++; - $empire_data{population} += $colony->population; - $empire_data{population_delta} += $colony->population_delta; - $empire_data{building_count} += $colony->building_count; - $empire_data{average_building_level} += $colony->average_building_level; - $empire_data{highest_building_level} = $colony->highest_building_level if ($colony->highest_building_level > $empire_data{highest_building_level}); - $empire_data{food_hour} += $colony->food_hour; - $empire_data{ore_hour} += $colony->ore_hour; - $empire_data{energy_hour} += $colony->energy_hour; - $empire_data{water_hour} += $colony->water_hour; - $empire_data{waste_hour} += $colony->waste_hour; - $empire_data{happiness_hour} += $colony->happiness_hour; - $empire_data{defense_success_rate} += $colony->defense_success_rate; - $empire_data{defense_success_rate_delta}+= $colony->defense_success_rate_delta; - $empire_data{offense_success_rate} += $colony->offense_success_rate; - $empire_data{offense_success_rate_delta}+= $colony->offense_success_rate_delta; - $empire_data{dirtiest} += $colony->dirtiest; - $empire_data{dirtiest_delta} += $colony->dirtiest_delta; - $empire_data{spy_count} += $colony->spy_count; - } - } - if ($empire_data{colony_count}) { - $empire_data{average_building_level} = $empire_data{average_building_level} / $empire_data{colony_count}; - $empire_data{offense_success_rate} = $empire_data{offense_success_rate} / $empire_data{colony_count}; - $empire_data{defense_success_rate} = $empire_data{defense_success_rate} / $empire_data{colony_count}; - } - $empire_data{empire_size} = $empire_data{colony_count} * $empire_data{population}; - my $log = $logs->search({empire_id => $empire->id},{rows=>1})->single; - if (defined $log) { - $empire_data{colony_count_delta} = $empire_data{colony_count} - $log->colony_count + $log->colony_count_delta; - $empire_data{empire_size_delta} = ($empire_data{colony_count_delta}) ? $empire_data{colony_count_delta} * $empire_data{population_delta} : $empire_data{population_delta}; - $log->update(\%empire_data); - } - else { - $logs->new(\%empire_data)->insert; + out('Summarizing Empires'); + my $logs = $db->resultset('Lacuna::DB::Result::Log::Empire'); + my $empires = $db->resultset('Lacuna::DB::Result::Empire')->search({ id => {'>' => 1} }); + my $colony_logs = $db->resultset('Lacuna::DB::Result::Log::Colony'); + my %mapping; + while (my $empire = $empires->next) { + out($empire->name); + my %empire_data = ( + date_stamp => DateTime->now, + university_level => $empire->university_level, + empire_id => $empire->id, + empire_name => $empire->name, + alliance_id => $empire->alliance_id, + ); + my %map_data = ( + empire_id => $empire->id, + empire_name => $empire->name, + alliance_id => $empire->alliance_id, + home_id => $empire->home_planet_id, + ); + out("Working on Empire: $map_data{empire_name} $map_data{alliance_id} $map_data{home_id}"); + if ($empire->alliance_id) { + $empire_data{alliance_name} = $empire->alliance->name; + $map_data{alliance_name} = $empire->alliance->name; + } + else { + $empire_data{alliance_name} = undef; + $map_data{alliance_id} = 0; + $map_data{alliance_name} = "Unalligned"; + } + my @map_colonies; + my $colonies = $colony_logs->search({empire_id => $empire->id}); + while ( my $colony = $colonies->next) { + if ($colony->is_space_station) { + $empire_data{influence} += $colony->influence; + $empire_data{space_station_count}++; + my %map_col = ( + type => "SS", + x => $colony->x, + y => $colony->y, + zone => $colony->zone, + ); + push @map_colonies, \%map_col; + } + else { + $empire_data{colony_count}++; + $empire_data{population} += $colony->population; + $empire_data{population_delta} += $colony->population_delta; + $empire_data{building_count} += $colony->building_count; + $empire_data{average_building_level} += $colony->average_building_level; + $empire_data{highest_building_level} = $colony->highest_building_level if ($colony->highest_building_level > $empire_data{highest_building_level}); + $empire_data{food_hour} += $colony->food_hour; + $empire_data{ore_hour} += $colony->ore_hour; + $empire_data{energy_hour} += $colony->energy_hour; + $empire_data{water_hour} += $colony->water_hour; + $empire_data{waste_hour} += $colony->waste_hour; + $empire_data{happiness_hour} += $colony->happiness_hour; + $empire_data{defense_success_rate} += $colony->defense_success_rate; + $empire_data{defense_success_rate_delta}+= $colony->defense_success_rate_delta; + $empire_data{offense_success_rate} += $colony->offense_success_rate; + $empire_data{offense_success_rate_delta}+= $colony->offense_success_rate_delta; + $empire_data{dirtiest} += $colony->dirtiest; + $empire_data{dirtiest_delta} += $colony->dirtiest_delta; + $empire_data{spy_count} += $colony->spy_count; + if ($colony->body_id eq $map_data{home_id}) { + my %map_col = ( + type => "Cap", + x => $colony->x, + y => $colony->y, + zone => $colony->zone, + ); + push @map_colonies, \%map_col; } + } + } + if (scalar @map_colonies > 0) { + $map_data{bodies} = \@map_colonies; + %{$mapping{$map_data{alliance_id}}} = %map_data; + } + if ($empire_data{colony_count}) { + $empire_data{average_building_level} = $empire_data{average_building_level} / $empire_data{colony_count}; + $empire_data{offense_success_rate} = $empire_data{offense_success_rate} / $empire_data{colony_count}; + $empire_data +{defense_success_rate} = $empire_data{defense_success_rate} / $empire_data{colony_count}; } + $empire_data{empire_size} = $empire_data{colony_count} * $empire_data{population}; + my $log = $logs->search({empire_id => $empire->id},{rows=>1})->single; + if (defined $log) { + $empire_data{colony_count_delta} = $empire_data{colony_count} - $log->colony_count + $log->colony_count_delta; + $empire_data{empire_size_delta} = ($empire_data{colony_count_delta}) ? $empire_data{colony_count_delta} * $empire_data{population_delta} : $empire_data{population_delta}; + $log->update(\%empire_data); + } + else { + $logs->new(\%empire_data)->insert; + } + } + my $ai = $db->resultset('Lacuna::DB::Result::Empire')->search({ id => {'<' => 0} }); + out('Summarizing AI for map'); + while (my $empire = $ai->next) { + my %map_data = ( + empire_id => $empire->id, + empire_name => $empire->name, + alliance_id => $empire->id, + alliance_name => $empire->name, + home_id => $empire->home_planet_id, + ); + out("Working on Empire: $map_data{empire_name} $map_data{alliance_id}"); + my @map_colonies; + my $colonies = $db->resultset('Lacuna::DB::Result::Map::Body')->search({ empire_id => $empire->id}); + while ( my $colony = $colonies->next) { + my %map_col = ( + x => $colony->x, + y => $colony->y, + zone => $colony->zone, + ); + my $btype = $colony->get_type; + if ($btype eq "space staion") { + $map_col{type} = "SS", + } + elsif ($colony->id == $map_data{home_id}) { + $map_col{type} = "Cap", + } + else { + $map_col{type} = "Col", + } + push @map_colonies, \%map_col; + } + if (scalar @map_colonies > 0) { + $map_data{bodies} = \@map_colonies; + %{$mapping{$map_data{alliance_id}}} = %map_data; + } + } + return (\%mapping); } sub summarize_colonies { @@ -372,6 +444,10 @@ sub summarize_colonies { happiness_hour => $planet->happiness_hour, empire_id => $planet->empire_id, empire_name => $planet->empire->name, + body_id => $planet->id, + x => $planet->x, + y => $planet->y, + zone => $planet->zone, ); if ($planet->class =~ /Station$/) { $colony_data{is_space_station} = 1; @@ -381,7 +457,7 @@ sub summarize_colonies { my $spies = $spy_logs->search({planet_id => $planet->id}); while (my $spy = $spies->next) { - $colony_data{spy_count}++; + $colony_data{spy_count}++; $colony_data{offense_success_rate} += $spy->offense_success_rate; $colony_data{offense_success_rate_delta} += $spy->offense_success_rate_delta; $colony_data{defense_success_rate} += $spy->defense_success_rate; @@ -454,6 +530,52 @@ sub summarize_spies { } } +sub output_map { + my $mapping = shift; + + my %output; + my $star_map_size = Lacuna->config->get('map_size'); + $output{map} = { + bounds => $star_map_size, + }; + for my $emp_id (keys %$mapping) { + my @info; + my @data; + for my $bod (@{$mapping->{$emp_id}->{bodies}}) { + my $info_str = + sprintf("%s (%s) -- %s : (%d,%d) [%s]", + $mapping->{$emp_id}->{empire_name}, + $mapping->{$emp_id}->{alliance_name}, + $bod->{type}, + $bod->{x}, + $bod->{y}, + $bod->{zone}); + my $data_str = [ $bod->{x}, $bod->{y} ]; + push @info, $info_str; + push @data, $data_str; + } + my $key = $mapping->{$emp_id}->{alliance_id}; + if (defined $output{alliances}->{$key}) { + push @{$output{alliances}->{$key}->{info}}, @info; + push @{$output{alliances}->{$key}->{data}}, @data; + } + else { + $output{alliances}->{$key}->{label} = $mapping->{$emp_id}->{alliance_name}; + $output{alliances}->{$key}->{alliance_id} = $mapping->{$emp_id}->{alliance_id}; + $output{alliances}->{$key}->{info} = \@info; + $output{alliances}->{$key}->{data} = \@data; + } + } + open(OUT, ">starmap.json"); + print OUT to_json(\%output, { pretty => 1}); + close(OUT); + out('Write To S3'); + my $config = Lacuna->config; + my $s3 = SOAP::Amazon::S3->new($config->get('access_key'), $config->get('secret_key'), { RaiseError => 1 }); + my $bucket = $s3->bucket($config->get('feeds/bucket')); + my $object = $bucket->putobject('starmap.json', to_json(\%out), { 'Content-Type' => 'application/json' }); + $object->acl('public'); +} # UTILITIES diff --git a/lib/Lacuna.pm b/lib/Lacuna.pm index 755bd99b..46cfd026 100644 --- a/lib/Lacuna.pm +++ b/lib/Lacuna.pm @@ -7,7 +7,7 @@ use Config::JSON; useall __PACKAGE__; -our $VERSION = 3.0830; +our $VERSION = 3.0831; my $config = Config::JSON->new('/data/Lacuna-Server/etc/lacuna.conf'); my $db = Lacuna::DB->connect($config->get('db/dsn'),$config->get('db/username'),$config->get('db/password'), { mysql_enable_utf8 => 1}); diff --git a/lib/Lacuna/DB/Result/Log/Colony.pm b/lib/Lacuna/DB/Result/Log/Colony.pm index 4decde9d..0157ea5c 100644 --- a/lib/Lacuna/DB/Result/Log/Colony.pm +++ b/lib/Lacuna/DB/Result/Log/Colony.pm @@ -32,6 +32,10 @@ __PACKAGE__->add_columns( dirtiest_delta => { data_type => 'int', size => 11, default_value => 0 }, is_space_station => { data_type => 'int', size => 11, default_value => 0 }, influence => { data_type => 'int', size => 11, default_value => 0 }, + x => { data_type => 'int', size => 11, default_value => 0 }, + y => { data_type => 'int', size => 11, default_value => 0 }, + body_id => { data_type => 'int', size => 11, default_value => 0 }, + zone => { data_type => 'varchar', size => 16, default_value => 0 }, ); after 'sqlt_deploy_hook' => sub { diff --git a/var/upgrades/3.0831.sql b/var/upgrades/3.0831.sql new file mode 100644 index 00000000..6887ae27 --- /dev/null +++ b/var/upgrades/3.0831.sql @@ -0,0 +1,4 @@ +alter table colony_log add column body_id int(11) not null default 0; +alter table colony_log add column x int(11) not null default 0; +alter table colony_log add column y int(11) not null default 0; +alter table colony_log add column zone varchar(16) not null default 0; diff --git a/var/www/public/changes.txt b/var/www/public/changes.txt index 643f90c9..b8a657e6 100644 --- a/var/www/public/changes.txt +++ b/var/www/public/changes.txt @@ -1,3 +1,6 @@ +3.0831: + - Add: Alliance mapping data available. + 3.0830: - Mod: Even more changes to SAWs. (We'll keep banging on these keys.) - Fix: Correspondance school is no longer an acredited option for spies. -- 2.51.2 From 92d86396aca3c6ff7feaa3efdec750b7b3111f00 Mon Sep 17 00:00:00 2001 From: lemming552 Date: Sun, 1 Jan 2012 11:21:45 -0700 Subject: [PATCH 10/75] Fixed a stupid typo. --- bin/summarize_server.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/summarize_server.pl b/bin/summarize_server.pl index 8855c1e1..c389aede 100644 --- a/bin/summarize_server.pl +++ b/bin/summarize_server.pl @@ -573,7 +573,7 @@ sub output_map { my $config = Lacuna->config; my $s3 = SOAP::Amazon::S3->new($config->get('access_key'), $config->get('secret_key'), { RaiseError => 1 }); my $bucket = $s3->bucket($config->get('feeds/bucket')); - my $object = $bucket->putobject('starmap.json', to_json(\%out), { 'Content-Type' => 'application/json' }); + my $object = $bucket->putobject('starmap.json', to_json(\%output), { 'Content-Type' => 'application/json' }); $object->acl('public'); } -- 2.51.2 From fbd902be3ef9ac4a2f514c6fa18404e95e3d0c24 Mon Sep 17 00:00:00 2001 From: lemming552 Date: Sun, 1 Jan 2012 11:40:13 -0700 Subject: [PATCH 11/75] Fixing utf-8 issue with starmap.json --- bin/summarize_server.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/summarize_server.pl b/bin/summarize_server.pl index c389aede..c2b57adc 100644 --- a/bin/summarize_server.pl +++ b/bin/summarize_server.pl @@ -573,7 +573,7 @@ sub output_map { my $config = Lacuna->config; my $s3 = SOAP::Amazon::S3->new($config->get('access_key'), $config->get('secret_key'), { RaiseError => 1 }); my $bucket = $s3->bucket($config->get('feeds/bucket')); - my $object = $bucket->putobject('starmap.json', to_json(\%output), { 'Content-Type' => 'application/json' }); + my $object = $bucket->putobject('starmap.json', to_json(\%output), { 'Content-Type' => 'application/json; charset=utf-8' }); $object->acl('public'); } -- 2.51.2 From 893d730b052d1d9542f5d7f1bcd81f0b2808bccc Mon Sep 17 00:00:00 2001 From: lemming552 Date: Sun, 1 Jan 2012 13:28:04 -0700 Subject: [PATCH 12/75] Added colony names for AI. --- bin/summarize_server.pl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bin/summarize_server.pl b/bin/summarize_server.pl index c2b57adc..3cb5f6ba 100644 --- a/bin/summarize_server.pl +++ b/bin/summarize_server.pl @@ -8,6 +8,7 @@ use Getopt::Long; use JSON; use SOAP::Amazon::S3; use Lacuna::Constants qw(SHIP_TYPES); +use utf8; $|=1; @@ -326,6 +327,7 @@ sub summarize_empires { x => $colony->x, y => $colony->y, zone => $colony->zone, + name => "", ); push @map_colonies, \%map_col; } @@ -355,6 +357,7 @@ sub summarize_empires { x => $colony->x, y => $colony->y, zone => $colony->zone, + name => "", ); push @map_colonies, \%map_col; } @@ -399,6 +402,7 @@ sub summarize_empires { x => $colony->x, y => $colony->y, zone => $colony->zone, + name => $colony->name, ); my $btype = $colony->get_type; if ($btype eq "space staion") { @@ -544,7 +548,7 @@ sub output_map { for my $bod (@{$mapping->{$emp_id}->{bodies}}) { my $info_str = sprintf("%s (%s) -- %s : (%d,%d) [%s]", - $mapping->{$emp_id}->{empire_name}, + ($bod->{name} ne "") ? $bod->{name} : $mapping->{$emp_id}->{empire_name}, $mapping->{$emp_id}->{alliance_name}, $bod->{type}, $bod->{x}, @@ -567,13 +571,13 @@ sub output_map { } } open(OUT, ">starmap.json"); - print OUT to_json(\%output, { pretty => 1}); + print OUT to_json(\%output, { pretty => 1, utf8 => 1}); close(OUT); - out('Write To S3'); + out('Write Map To S3'); my $config = Lacuna->config; my $s3 = SOAP::Amazon::S3->new($config->get('access_key'), $config->get('secret_key'), { RaiseError => 1 }); my $bucket = $s3->bucket($config->get('feeds/bucket')); - my $object = $bucket->putobject('starmap.json', to_json(\%output), { 'Content-Type' => 'application/json; charset=utf-8' }); + my $object = $bucket->putobject('starmap.json', to_json(\%output, {utf8 => 1}), { 'Content-Type' => 'application/json; charset=utf-8' }); $object->acl('public'); } -- 2.51.2 From 08cacd240392dd110e24c11e7386b553aba04c4a Mon Sep 17 00:00:00 2001 From: lemming552 Date: Sun, 1 Jan 2012 17:25:15 -0700 Subject: [PATCH 13/75] Correct spelling of unaligned. --- bin/summarize_server.pl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/summarize_server.pl b/bin/summarize_server.pl index 3cb5f6ba..8f17c920 100644 --- a/bin/summarize_server.pl +++ b/bin/summarize_server.pl @@ -314,7 +314,7 @@ sub summarize_empires { else { $empire_data{alliance_name} = undef; $map_data{alliance_id} = 0; - $map_data{alliance_name} = "Unalligned"; + $map_data{alliance_name} = "Unaligned"; } my @map_colonies; my $colonies = $colony_logs->search({empire_id => $empire->id}); @@ -365,7 +365,7 @@ sub summarize_empires { } if (scalar @map_colonies > 0) { $map_data{bodies} = \@map_colonies; - %{$mapping{$map_data{alliance_id}}} = %map_data; + %{$mapping{$map_data{empire_id}}} = %map_data; } if ($empire_data{colony_count}) { $empire_data{average_building_level} = $empire_data{average_building_level} / $empire_data{colony_count}; @@ -418,7 +418,7 @@ sub summarize_empires { } if (scalar @map_colonies > 0) { $map_data{bodies} = \@map_colonies; - %{$mapping{$map_data{alliance_id}}} = %map_data; + %{$mapping{$map_data{empire_id}}} = %map_data; } } return (\%mapping); -- 2.51.2 From e0ba6318e2bfaa33280d23f672ecd19e91fe089d Mon Sep 17 00:00:00 2001 From: ian docherty Date: Sun, 1 Jan 2012 20:10:25 -0500 Subject: [PATCH 14/75] Dillon forge can not now be upgraded with Halls --- lib/Lacuna.pm | 2 +- .../Building/Permanent/HallsOfVrbansk.pm | 18 ++++++++++++------ var/www/public/changes.txt | 5 +++++ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/Lacuna.pm b/lib/Lacuna.pm index 5cb59514..755bd99b 100644 --- a/lib/Lacuna.pm +++ b/lib/Lacuna.pm @@ -7,7 +7,7 @@ use Config::JSON; useall __PACKAGE__; -our $VERSION = 3.0829; +our $VERSION = 3.0830; my $config = Config::JSON->new('/data/Lacuna-Server/etc/lacuna.conf'); my $db = Lacuna::DB->connect($config->get('db/dsn'),$config->get('db/username'),$config->get('db/password'), { mysql_enable_utf8 => 1}); diff --git a/lib/Lacuna/DB/Result/Building/Permanent/HallsOfVrbansk.pm b/lib/Lacuna/DB/Result/Building/Permanent/HallsOfVrbansk.pm index 012ad97c..15dad5c2 100644 --- a/lib/Lacuna/DB/Result/Building/Permanent/HallsOfVrbansk.pm +++ b/lib/Lacuna/DB/Result/Building/Permanent/HallsOfVrbansk.pm @@ -33,22 +33,28 @@ after finish_upgrade => sub { sub get_halls { my $self = shift; - return $self->body->get_buildings_of_class('Lacuna::DB::Result::Building::Permanent::HallsOfVrbansk'); + return $self->body->get_buildings_of_class('Lacuna::DB::Result::Building::Permanent::HallsOfVrbansk')->search({ + is_upgrading => 0, + }); } sub get_upgradable_buildings { my ($self) = @_; - my $body = $self->body; + my $body = $self->body; $body->update; - my @halls = $self->get_halls->get_column('id')->all; + my @halls = $self->get_halls->get_column('id')->all; my $max_level = scalar @halls; $max_level = 30 if $max_level > 30; - return $body->buildings->search({ + my $rs = $body->buildings->search({ level => { '<' => $max_level }, - class => { like => 'Lacuna::DB::Result::Building::Permanent::%' }, - id => { 'not in' => \@halls }, + -and => [ + class => {like => 'Lacuna::DB::Result::Building::Permanent::%'}, + class => {'!=' => 'Lacuna::DB::Result::Building::Permanent::TheDillonForge'}, + class => {'!=' => 'Lacuna::DB::Result::Building::Permanent::HallsOfVrbansk'}, + ], is_upgrading => 0, }); + return $rs; } use constant name => 'Halls of Vrbansk'; diff --git a/var/www/public/changes.txt b/var/www/public/changes.txt index f851beea..6c3e66eb 100644 --- a/var/www/public/changes.txt +++ b/var/www/public/changes.txt @@ -1,3 +1,8 @@ +3.0830: + - Fix: Dillon Forge can not be upgraded with Halls of Vrbansk + - Note: Dillon Forge cannot be demolished/plan stripped/upgraded + - Fix: You cannot now sacrifice Halls of Vrbansk that have not completed their build + 3.0829: - Add: Added bulk trading in plans/glyphs/ships to SST -- 2.51.2 From af1659eed4a357871ff6b2cd46a45735b7b7b8c5 Mon Sep 17 00:00:00 2001 From: lemming552 Date: Sun, 1 Jan 2012 22:11:26 -0700 Subject: [PATCH 15/75] More S3 UTF fun --- bin/summarize_server.pl | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/bin/summarize_server.pl b/bin/summarize_server.pl index 8f17c920..165a30b9 100644 --- a/bin/summarize_server.pl +++ b/bin/summarize_server.pl @@ -129,25 +129,25 @@ sub generate_overview { # flesh out orbits out('Flesh Out Orbital Stats'); foreach my $orbit (1..8) { - out($orbit); - $out{orbits}{$orbit} = { - inhabited => $bodies->search({empire_id => {'>', 0}, orbit => $orbit})->count, - bodies => $bodies->search({orbit => $orbit})->count, - } + out($orbit); + $out{orbits}{$orbit} = { + inhabited => $bodies->search({empire_id => {'>', 0}, orbit => $orbit})->count, + bodies => $bodies->search({orbit => $orbit})->count, + } } # flesh out buildings out('Flesh Out Building Stats'); my $distinct = $buildings->search(undef, { group_by => ['class'] })->get_column('class'); while (my $class = $distinct->next) { - out($class); - my $type_rs = $buildings->search({class=>$class}); - my $count = $type_rs->count; - $out{buildings}{types}{$class->name} = { - average_level => $type_rs->get_column('level')->func('avg'), - highest_level => $type_rs->get_column('level')->max, - count => $count, - }; + out($class); + my $type_rs = $buildings->search({class=>$class}); + my $count = $type_rs->count; + $out{buildings}{types}{$class->name} = { + average_level => $type_rs->get_column('level')->func('avg'), + highest_level => $type_rs->get_column('level')->max, + count => $count, + }; } # flesh out ships @@ -570,14 +570,15 @@ sub output_map { $output{alliances}->{$key}->{data} = \@data; } } - open(OUT, ">starmap.json"); - print OUT to_json(\%output, { pretty => 1, utf8 => 1}); + my $json_txt = JSON->new->utf8->encode(\%output); + open(OUT, ">:utf8:", "starmap.json"); + print OUT $json_txt; close(OUT); out('Write Map To S3'); my $config = Lacuna->config; my $s3 = SOAP::Amazon::S3->new($config->get('access_key'), $config->get('secret_key'), { RaiseError => 1 }); my $bucket = $s3->bucket($config->get('feeds/bucket')); - my $object = $bucket->putobject('starmap.json', to_json(\%output, {utf8 => 1}), { 'Content-Type' => 'application/json; charset=utf-8' }); + my $object = $bucket->putobject('starmap.json', $json_txt, { 'Content-Type' => 'application/json; charset=utf-8' }); $object->acl('public'); } -- 2.51.2 From d990d41ca20266e8b70c1ca8501ba284a78beeea Mon Sep 17 00:00:00 2001 From: lemming552 Date: Sun, 1 Jan 2012 22:11:26 -0700 Subject: [PATCH 16/75] More S3 UTF fun --- bin/summarize_server.pl | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/bin/summarize_server.pl b/bin/summarize_server.pl index 8f17c920..165a30b9 100644 --- a/bin/summarize_server.pl +++ b/bin/summarize_server.pl @@ -129,25 +129,25 @@ sub generate_overview { # flesh out orbits out('Flesh Out Orbital Stats'); foreach my $orbit (1..8) { - out($orbit); - $out{orbits}{$orbit} = { - inhabited => $bodies->search({empire_id => {'>', 0}, orbit => $orbit})->count, - bodies => $bodies->search({orbit => $orbit})->count, - } + out($orbit); + $out{orbits}{$orbit} = { + inhabited => $bodies->search({empire_id => {'>', 0}, orbit => $orbit})->count, + bodies => $bodies->search({orbit => $orbit})->count, + } } # flesh out buildings out('Flesh Out Building Stats'); my $distinct = $buildings->search(undef, { group_by => ['class'] })->get_column('class'); while (my $class = $distinct->next) { - out($class); - my $type_rs = $buildings->search({class=>$class}); - my $count = $type_rs->count; - $out{buildings}{types}{$class->name} = { - average_level => $type_rs->get_column('level')->func('avg'), - highest_level => $type_rs->get_column('level')->max, - count => $count, - }; + out($class); + my $type_rs = $buildings->search({class=>$class}); + my $count = $type_rs->count; + $out{buildings}{types}{$class->name} = { + average_level => $type_rs->get_column('level')->func('avg'), + highest_level => $type_rs->get_column('level')->max, + count => $count, + }; } # flesh out ships @@ -570,14 +570,15 @@ sub output_map { $output{alliances}->{$key}->{data} = \@data; } } - open(OUT, ">starmap.json"); - print OUT to_json(\%output, { pretty => 1, utf8 => 1}); + my $json_txt = JSON->new->utf8->encode(\%output); + open(OUT, ">:utf8:", "starmap.json"); + print OUT $json_txt; close(OUT); out('Write Map To S3'); my $config = Lacuna->config; my $s3 = SOAP::Amazon::S3->new($config->get('access_key'), $config->get('secret_key'), { RaiseError => 1 }); my $bucket = $s3->bucket($config->get('feeds/bucket')); - my $object = $bucket->putobject('starmap.json', to_json(\%output, {utf8 => 1}), { 'Content-Type' => 'application/json; charset=utf-8' }); + my $object = $bucket->putobject('starmap.json', $json_txt, { 'Content-Type' => 'application/json; charset=utf-8' }); $object->acl('public'); } -- 2.51.2 From 466a7728210ea624b89fa7775c4088d9f57aeda2 Mon Sep 17 00:00:00 2001 From: ian docherty Date: Mon, 2 Jan 2012 07:02:36 -0500 Subject: [PATCH 17/75] More efficient deletion of outdated email --- bin/clean_up_mail.pl | 11 ++++++++--- lib/Lacuna.pm | 2 +- var/www/public/changes.txt | 4 ++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/bin/clean_up_mail.pl b/bin/clean_up_mail.pl index 50e539d5..2d249f80 100644 --- a/bin/clean_up_mail.pl +++ b/bin/clean_up_mail.pl @@ -23,10 +23,15 @@ our $db = Lacuna->db; out('Deleting AI Mail Items'); my $mail = $db->resultset('Lacuna::DB::Result::Message'); -$mail->search({ to_id => { '<=' => 1 }})->delete_all; +$mail->search({ to_id => { '<=' => 1 }})->delete; -out('Deleting Outdated Player Mail Items'); -$mail->search({ has_trashed => 1, date_sent => { '<' => $date_ended }})->delete_all; +out('Deleting Outdated Trashed Player Mail Items'); +$mail->search({ has_trashed => 1, date_sent => { '<' => $date_ended }})->delete; + +my $date_ended = DateTime->now->subtract( days => 30 ); + +out('Deleting All Outdated Player Mail Items'); +$mail->search({ date_sent => { '<' => $date_ended }})->delete; my $finish = time; out('Finished'); diff --git a/lib/Lacuna.pm b/lib/Lacuna.pm index 755bd99b..46cfd026 100644 --- a/lib/Lacuna.pm +++ b/lib/Lacuna.pm @@ -7,7 +7,7 @@ use Config::JSON; useall __PACKAGE__; -our $VERSION = 3.0830; +our $VERSION = 3.0831; my $config = Config::JSON->new('/data/Lacuna-Server/etc/lacuna.conf'); my $db = Lacuna::DB->connect($config->get('db/dsn'),$config->get('db/username'),$config->get('db/password'), { mysql_enable_utf8 => 1}); diff --git a/var/www/public/changes.txt b/var/www/public/changes.txt index 6c3e66eb..bc449a50 100644 --- a/var/www/public/changes.txt +++ b/var/www/public/changes.txt @@ -1,3 +1,7 @@ +3.0831: + - Fix: Trashed mail older than 7 days will be deleted regularly + - Mod: All mail older than 30 days will be deleted regularly + 3.0830: - Fix: Dillon Forge can not be upgraded with Halls of Vrbansk - Note: Dillon Forge cannot be demolished/plan stripped/upgraded -- 2.51.2 From aa8d011d3f6075dba9be59af112e0a19a2b436c4 Mon Sep 17 00:00:00 2001 From: ian docherty Date: Mon, 2 Jan 2012 09:07:12 -0500 Subject: [PATCH 18/75] Modified DeLambert so they can attack hourly --- lib/Lacuna/AI/DeLambert.pm | 16 +++++++++++++--- lib/Lacuna/Web/Admin.pm | 21 +++++++++++++-------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/lib/Lacuna/AI/DeLambert.pm b/lib/Lacuna/AI/DeLambert.pm index 01044b6e..877931a5 100644 --- a/lib/Lacuna/AI/DeLambert.pm +++ b/lib/Lacuna/AI/DeLambert.pm @@ -417,18 +417,23 @@ sub retaliate { say "#### Retaliate! ####"; my $empire = $self->empire; $self->scratch->discard_changes; - my $attack = $self->scratch->pad->{attack}; + my $scratch_pad = $self->scratch->pad; + my $attack = $scratch_pad->{attack}; my @del_colonies = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->search({ empire_id => -9, }); + say " Checking daily attack time '".DateTime->now->hour."'"; + my $attack_daily = DateTime->now->hour eq 14; + TARGET: foreach my $target_id (keys %$attack) { my $target = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->find($target_id); if ($target) { - say " Target empire '".$target->name."' status '".$attack->{$target_id}{status}."'"; - if ($attack->{$target_id}{status} eq "Attacking") { + my $freq = $attack->{$target_id}{frequency} || 'never'; + say " Target empire '".$target->name."' frequency '$freq'"; + if ($freq eq 'hourly' or $freq eq 'once' or ($freq eq 'daily' and $attack_daily)) { my $target_colony_id = $attack->{$target_id}{colony_id}; my $num_sweepers = $attack->{$target_id}{sweepers}; my $num_scows = $attack->{$target_id}{scows}; @@ -504,6 +509,11 @@ TARGET: $ship->date_available($arrival_time); $ship->update; } + if ($freq eq 'once') { + $scratch_pad->{attack}{$target_id}{frequency} = 'never'; + $self->scratch->pad($scratch_pad); + $self->scratch->update; + } } } } diff --git a/lib/Lacuna/Web/Admin.pm b/lib/Lacuna/Web/Admin.pm index c37e1ca9..faa9a9e3 100644 --- a/lib/Lacuna/Web/Admin.pm +++ b/lib/Lacuna/Web/Admin.pm @@ -1229,13 +1229,13 @@ sub www_delambert_war { my ($scratch) = Lacuna->db->resultset('Lacuna::DB::Result::AIScratchPad')->search({ai_empire_id => -9, body_id => 0}); my $scratchpad = $scratch->pad; - if ($request->param('action')) { + if ($request->param('submit')) { $scratchpad->{attack}{$request->param('attacker_id')} = { - status => $request->param('action') eq 'Hold' ? 'Holding' : 'Attacking', sweepers => $request->param('sweepers'), scows => $request->param('scows'), snarks => $request->param('snarks'), colony_id => $request->param('colony_id'), + frequency => $request->param('frequency'), }; $scratch->pad($scratchpad); $scratch->update; @@ -1265,7 +1265,7 @@ sub www_delambert_war { # Sort the attackers so that those who have done the most un-retaliated damage are shown first my @worst_attackers = sort {( $defence{$b}{weight} - defined $attack{$b} ? $attack{$b} : 0) <=> ( $defence{$a}{weight} - defined $attack{$a} ? $attack{$a} : 0 ) } keys %defence; - $out .= "\n"; + $out .= "
AttackerVictoriesDefeatsSpy HoursCounter AttacksWeightStatusColonyAttack SweepersAttack ScowsAttack SnarkAction
\n"; ATTACKER: foreach my $attacker (@worst_attackers) { my $attack_empire = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->find($attacker); @@ -1279,23 +1279,22 @@ ATTACKER: if (not defined $scratchpad->{attack}{$attacker}) { $scratchpad->{attack}{$attacker} = { - status => 'Holding', colony_id => $colonies[0]->id, sweepers => 1000, snarks => 200, scows => 200, + frequency => 'Once', }; $scratch->pad($scratchpad); $scratch->update; } - my $status = $scratchpad->{attack}{$attacker}{status}; my $sweepers = $scratchpad->{attack}{$attacker}{sweepers}; my $snarks = $scratchpad->{attack}{$attacker}{snarks}; my $scows = $scratchpad->{attack}{$attacker}{scows}; - my $action = $status eq 'Holding' ? 'Attack' : 'Hold'; + my $frequency = $scratchpad->{attack}{$attacker}{frequency}; my $counter_attacks = defined $attack{$attacker} ? $attack{$attacker} : 0; - $out .= ""; + $out .= ""; $out .= ""; $out .= ""; + $out .= ""; $out .= ""; $out .= ""; $out .= ""; $out .= ""; - $out .= ""; + $out .= ""; } $out .= "
AttackerVictoriesDefeatsSpy HoursCounter AttacksWeightColonyFrequencyAttack SweepersAttack ScowsAttack SnarkAction
".$attack_empire->name."".$defence{$attacker}{attack_victories}."".$defence{$attacker}{defense_victories}."".$defence{$attacker}{attack_spy_hours}."$counter_attacks".$defence{$attacker}{weight}."$status
".$attack_empire->name."".$defence{$attacker}{attack_victories}."".$defence{$attacker}{defense_victories}."".$defence{$attacker}{attack_spy_hours}."$counter_attacks".$defence{$attacker}{weight}."
\n"; return $self->wrap($out); -- 2.51.2 From aa3d2a2b378582e346dc9740911248008177b8dd Mon Sep 17 00:00:00 2001 From: ian docherty Date: Mon, 2 Jan 2012 12:14:54 -0500 Subject: [PATCH 19/75] More efficient handling of incoming ships in status --- lib/Lacuna/DB/Result/Map/Body/Planet.pm | 121 +++++++++++++++++------- 1 file changed, 87 insertions(+), 34 deletions(-) diff --git a/lib/Lacuna/DB/Result/Map/Body/Planet.pm b/lib/Lacuna/DB/Result/Map/Body/Planet.pm index c9e626a9..0b2b3426 100644 --- a/lib/Lacuna/DB/Result/Map/Body/Planet.pm +++ b/lib/Lacuna/DB/Result/Map/Body/Planet.pm @@ -179,49 +179,102 @@ around get_status => sub { if ($self->needs_recalc) { $self->tick; # in case what we just did is going to change our stats } - unless ($empire->is_isolationist) { # don't need to warn about incoming ships if can't be attacked + if (not $empire->is_isolationist) { # don't need to warn about incoming ships if can't be attacked my $now = time; - my $incoming_ships = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->search( - { - foreign_body_id => $self->id, - direction => 'out', - task => 'Travelling', - } - ); - my @colonies; - my @allies; + my $foreign_bodies; - while (my $ship = $incoming_ships->next) { - if ($ship->date_available->epoch <= $now) { -# $ship->foreign_body($self); -# $ship->body->tick; - $foreign_bodies->{$ship->body_id} = 1; - } - else { - unless (scalar @colonies) { # we don't look it up unless we have incoming - @colonies = $empire->planets->get_column('id')->all; - } - unless (scalar @allies) { # we don't look it up unless we have incoming - my $alliance = $empire->alliance if $empire->alliance_id; - if (defined $alliance) { - @allies = $alliance->members->get_column('id')->all; - } - } - push @{$out->{incoming_foreign_ships}}, { - date_arrives => $ship->date_available_formatted, - is_own => ($ship->body_id ~~ \@colonies) ? 1 : 0, - is_ally => ($ship->body->empire_id ~~ \@allies) ? 1 : 0, - id => $ship->id, - }; - } + # Process all ships that have already arrived + my $incoming_rs = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->search({ + foreign_body_id => $self->id, + direction => 'out', + task => 'Travelling', + date_available => {'<' => $now.''}, + }); + while (my $ship = $incoming_rs->next) { + $foreign_bodies->{$ship->body_id} = 1; } - # tick all bodies where ships have arrived foreach my $body_id (keys %$foreign_bodies) { my $body = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->find($body_id); if ($body) { $body->tick; } } + + my $num_incoming_ally = 0; + my @incoming_ally; + # If we are in an alliance, all ships coming from ally (which are not ourself) + if ($self->alliance_id) { + my $incoming_ally_rs = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->search({ + foreign_body_id => $self->id, + direction => 'out', + task => 'Travelling', + 'body.empire_id' => {'!=' => $self->empire_id}, + 'body.alliance_id' => $self->alliance_id, + },{ + join => 'body', + order_by => 'date_available', + }); + $num_incoming_ally = $incoming_ally_rs->count; + @incoming_ally = $incoming_ally_rs->search({},{rows => 10}); + } + # All ships coming from ourself + my $incoming_own_rs = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->search({ + foreign_body_id => $self->id, + direction => 'out', + task => 'Travelling', + 'body.empire_id' => $self->empire_id, + },{ + join => 'body', + order_by => 'date_available', + }); + my $num_incoming_own = $incoming_own_rs->count; + my @incoming_own = $incoming_own_rs->search({},{rows => 10}); + + # All enemy incoming + my $incoming_enemy_rs = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->search({ + foreign_body_id => $self->id, + direction => 'out', + task => 'Travelling', + 'body.empire_id' => {'!=' => $self->empire_id}, + },{ + join => 'body', + order_by => 'date_available', + }); + if ($self->alliance_id) { + $incoming_enemy_rs = $incoming_enemy_rs->search({ + 'body.alliance_id' => {'!=' => $self->alliance_id}, + }); + } + my $num_incoming_enemy = $incoming_enemy_rs->count; + my @incoming_enemy = $incoming_enemy_rs->search({},{rows => 20}); + + $out->{num_incoming_enemy} = $num_incoming_enemy; + foreach my $ship (@incoming_enemy) { + push @{$out->{incoming_foreign_ships}}, { + date_arrives => $ship->date_available_formatted, + is_own => 0, + is_ally => 0, + id => $ship->id, + }; + } + $out->{num_incoming_ally} = $num_incoming_ally; + foreach my $ship (@incoming_ally) { + push @{$out->{incoming_foreign_ships}}, { + date_arrives => $ship->date_available_formatted, + is_own => 0, + is_ally => 1, + id => $ship->id, + }; + } + $out->{num_incoming_own} = $num_incoming_own; + foreach my $ship (@incoming_own) { + push @{$out->{incoming_foreign_ships}}, { + date_arrives => $ship->date_available_formatted, + is_own => 1, + is_ally => 0, + id => $ship->id, + }; + } } $out->{needs_surface_refresh} = $self->needs_surface_refresh; if ($self->needs_surface_refresh) { -- 2.51.2 From 36173330b0ee5d4565de32335f3b6e915d48fe9a Mon Sep 17 00:00:00 2001 From: lemming552 Date: Mon, 2 Jan 2012 10:32:53 -0700 Subject: [PATCH 20/75] Commented out mapping writes to file. --- bin/summarize_server.pl | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/bin/summarize_server.pl b/bin/summarize_server.pl index 165a30b9..c38ba2a3 100644 --- a/bin/summarize_server.pl +++ b/bin/summarize_server.pl @@ -115,7 +115,7 @@ sub generate_overview { asteroids => 'Lacuna::DB::Result::Map::Body::Asteroid%', ); foreach my $key (keys %body_types) { - out($key); + out($key); my $type = $bodies->search({class => {like => $body_types{$key}}}); $out{bodies}{types}{$key} = { count => $type->count, @@ -306,7 +306,6 @@ sub summarize_empires { alliance_id => $empire->alliance_id, home_id => $empire->home_planet_id, ); - out("Working on Empire: $map_data{empire_name} $map_data{alliance_id} $map_data{home_id}"); if ($empire->alliance_id) { $empire_data{alliance_name} = $empire->alliance->name; $map_data{alliance_name} = $empire->alliance->name; @@ -394,7 +393,6 @@ sub summarize_empires { alliance_name => $empire->name, home_id => $empire->home_planet_id, ); - out("Working on Empire: $map_data{empire_name} $map_data{alliance_id}"); my @map_colonies; my $colonies = $db->resultset('Lacuna::DB::Result::Map::Body')->search({ empire_id => $empire->id}); while ( my $colony = $colonies->next) { @@ -537,6 +535,10 @@ sub summarize_spies { sub output_map { my $mapping = shift; +# my $map_txt = JSON->new->utf8->encode($mapping); +# open(OUT, ">:utf8:", "mapping.json"); +# print OUT $map_txt; +# close(OUT); my %output; my $star_map_size = Lacuna->config->get('map_size'); $output{map} = { @@ -570,10 +572,10 @@ sub output_map { $output{alliances}->{$key}->{data} = \@data; } } - my $json_txt = JSON->new->utf8->encode(\%output); - open(OUT, ">:utf8:", "starmap.json"); - print OUT $json_txt; - close(OUT); +# my $json_txt = JSON->new->utf8->encode(\%output); +# open(OUT, ">:utf8:", "starmap.json"); +# print OUT $json_txt; +# close(OUT); out('Write Map To S3'); my $config = Lacuna->config; my $s3 = SOAP::Amazon::S3->new($config->get('access_key'), $config->get('secret_key'), { RaiseError => 1 }); -- 2.51.2 From 6283cee0bc10f0f3d4fe2202f53654345bf33fc0 Mon Sep 17 00:00:00 2001 From: ian docherty Date: Mon, 2 Jan 2012 14:01:32 -0500 Subject: [PATCH 21/75] Planet tick when ships are overdue --- lib/Lacuna/DB/Result/Map/Body/Planet.pm | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/Lacuna/DB/Result/Map/Body/Planet.pm b/lib/Lacuna/DB/Result/Map/Body/Planet.pm index 0b2b3426..c7bc28c1 100644 --- a/lib/Lacuna/DB/Result/Map/Body/Planet.pm +++ b/lib/Lacuna/DB/Result/Map/Body/Planet.pm @@ -184,11 +184,12 @@ around get_status => sub { my $foreign_bodies; # Process all ships that have already arrived + my $incoming_rs = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->search({ foreign_body_id => $self->id, direction => 'out', task => 'Travelling', - date_available => {'<' => $now.''}, + date_available => {'<' => DateTime->now.''}, }); while (my $ship = $incoming_rs->next) { $foreign_bodies->{$ship->body_id} = 1; @@ -250,7 +251,7 @@ around get_status => sub { $out->{num_incoming_enemy} = $num_incoming_enemy; foreach my $ship (@incoming_enemy) { - push @{$out->{incoming_foreign_ships}}, { + push @{$out->{incoming_enemy_ships}}, { date_arrives => $ship->date_available_formatted, is_own => 0, is_ally => 0, @@ -259,7 +260,7 @@ around get_status => sub { } $out->{num_incoming_ally} = $num_incoming_ally; foreach my $ship (@incoming_ally) { - push @{$out->{incoming_foreign_ships}}, { + push @{$out->{incoming_ally_ships}}, { date_arrives => $ship->date_available_formatted, is_own => 0, is_ally => 1, @@ -268,7 +269,7 @@ around get_status => sub { } $out->{num_incoming_own} = $num_incoming_own; foreach my $ship (@incoming_own) { - push @{$out->{incoming_foreign_ships}}, { + push @{$out->{incoming_own_ships}}, { date_arrives => $ship->date_available_formatted, is_own => 1, is_ally => 0, -- 2.51.2 From 044e9e410f9e6e3a3e2653d3e9e78198b0039812 Mon Sep 17 00:00:00 2001 From: lemming552 Date: Mon, 2 Jan 2012 14:47:43 -0700 Subject: [PATCH 22/75] Neutral is a better word for the unaligned empires. --- bin/summarize_server.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/summarize_server.pl b/bin/summarize_server.pl index c38ba2a3..50cbbda4 100644 --- a/bin/summarize_server.pl +++ b/bin/summarize_server.pl @@ -313,7 +313,7 @@ sub summarize_empires { else { $empire_data{alliance_name} = undef; $map_data{alliance_id} = 0; - $map_data{alliance_name} = "Unaligned"; + $map_data{alliance_name} = "Neutral"; } my @map_colonies; my $colonies = $colony_logs->search({empire_id => $empire->id}); -- 2.51.2 From 4912f188dd133b37913af80fb6ab111350e864de Mon Sep 17 00:00:00 2001 From: lemming552 Date: Mon, 2 Jan 2012 15:32:18 -0700 Subject: [PATCH 23/75] Adding starmap indexes. (still need method for getting data) --- var/www/public/starmap/css/layout.css | 6 + var/www/public/starmap/index.html | 252 + var/www/public/starmap/js/jquery.flot.js | 2119 ++++++++ .../starmap/js/jquery.flot.selection.js | 299 ++ var/www/public/starmap/js/jquery.js | 4376 +++++++++++++++++ var/www/public/starmap/misc/goatsea.png | Bin 0 -> 118423 bytes var/www/public/starmap/sectors.html | 285 ++ var/www/public/starmap/starmap.json | 1 + 8 files changed, 7338 insertions(+) create mode 100644 var/www/public/starmap/css/layout.css create mode 100644 var/www/public/starmap/index.html create mode 100644 var/www/public/starmap/js/jquery.flot.js create mode 100644 var/www/public/starmap/js/jquery.flot.selection.js create mode 100644 var/www/public/starmap/js/jquery.js create mode 100644 var/www/public/starmap/misc/goatsea.png create mode 100644 var/www/public/starmap/sectors.html create mode 100644 var/www/public/starmap/starmap.json diff --git a/var/www/public/starmap/css/layout.css b/var/www/public/starmap/css/layout.css new file mode 100644 index 00000000..e24a910f --- /dev/null +++ b/var/www/public/starmap/css/layout.css @@ -0,0 +1,6 @@ +body { + font-family:"Lucida Console", Times, serif; + font-size: 16px; + margin: 50px; + max-width: 1500px; +} diff --git a/var/www/public/starmap/index.html b/var/www/public/starmap/index.html new file mode 100644 index 00000000..8fc40ec7 --- /dev/null +++ b/var/www/public/starmap/index.html @@ -0,0 +1,252 @@ + + + + + Lacuna Alliance Starmap + + + + + + + +

Lacuna Alliance Starmap

+ + +
+
+
+ +
+
+ +

+
+ +

Click and drag on either map to zoom in or out. Click on two points to calculate distance. Reset Zoom Highlight Zones

+ +

Show:

+
+

Brought to you by the Sea of Goats

+ +
+ + + + + diff --git a/var/www/public/starmap/js/jquery.flot.js b/var/www/public/starmap/js/jquery.flot.js new file mode 100644 index 00000000..6534a468 --- /dev/null +++ b/var/www/public/starmap/js/jquery.flot.js @@ -0,0 +1,2119 @@ +/* Javascript plotting library for jQuery, v. 0.6. + * + * Released under the MIT license by IOLA, December 2007. + * + */ + +// first an inline dependency, jquery.colorhelpers.js, we inline it here +// for convenience + +/* Plugin for jQuery for working with colors. + * + * Version 1.0. + * + * Inspiration from jQuery color animation plugin by John Resig. + * + * Released under the MIT license by Ole Laursen, October 2009. + * + * Examples: + * + * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() + * var c = $.color.extract($("#mydiv"), 'background-color'); + * console.log(c.r, c.g, c.b, c.a); + * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" + * + * Note that .scale() and .add() work in-place instead of returning + * new objects. + */ +(function(){jQuery.color={};jQuery.color.make=function(E,D,B,C){var F={};F.r=E||0;F.g=D||0;F.b=B||0;F.a=C!=null?C:1;F.add=function(I,H){for(var G=0;G=1){return"rgb("+[F.r,F.g,F.b].join(",")+")"}else{return"rgba("+[F.r,F.g,F.b,F.a].join(",")+")"}};F.normalize=function(){function G(I,J,H){return JH?H:J)}F.r=G(0,parseInt(F.r),255);F.g=G(0,parseInt(F.g),255);F.b=G(0,parseInt(F.b),255);F.a=G(0,F.a,1);return F};F.clone=function(){return jQuery.color.make(F.r,F.b,F.g,F.a)};return F.normalize()};jQuery.color.extract=function(C,B){var D;do{D=C.css(B).toLowerCase();if(D!=""&&D!="transparent"){break}C=C.parent()}while(!jQuery.nodeName(C.get(0),"body"));if(D=="rgba(0, 0, 0, 0)"){D="transparent"}return jQuery.color.parse(D)};jQuery.color.parse=function(E){var D,B=jQuery.color.make;if(D=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(E)){return B(parseInt(D[1],10),parseInt(D[2],10),parseInt(D[3],10))}if(D=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(E)){return B(parseInt(D[1],10),parseInt(D[2],10),parseInt(D[3],10),parseFloat(D[4]))}if(D=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(E)){return B(parseFloat(D[1])*2.55,parseFloat(D[2])*2.55,parseFloat(D[3])*2.55)}if(D=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(E)){return B(parseFloat(D[1])*2.55,parseFloat(D[2])*2.55,parseFloat(D[3])*2.55,parseFloat(D[4]))}if(D=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(E)){return B(parseInt(D[1],16),parseInt(D[2],16),parseInt(D[3],16))}if(D=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(E)){return B(parseInt(D[1]+D[1],16),parseInt(D[2]+D[2],16),parseInt(D[3]+D[3],16))}var C=jQuery.trim(E).toLowerCase();if(C=="transparent"){return B(255,255,255,0)}else{D=A[C];return B(D[0],D[1],D[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(); + +// the actual Flot code +(function($) { + function Plot(placeholder, data_, options_, plugins) { + // data is on the form: + // [ series1, series2 ... ] + // where series is either just the data as [ [x1, y1], [x2, y2], ... ] + // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } + + var series = [], + options = { + // the color theme used for graphs + colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], + legend: { + show: true, + noColumns: 1, // number of colums in legend table + labelFormatter: null, // fn: string -> string + labelBoxBorderColor: "#ccc", // border color for the little label boxes + container: null, // container (as jQuery object) to put legend in, null means default on top of graph + position: "ne", // position of default legend container within plot + margin: 5, // distance from grid edge to default legend container within plot + backgroundColor: null, // null means auto-detect + backgroundOpacity: 0.85 // set to 0 to avoid background + }, + xaxis: { + mode: null, // null or "time" + transform: null, // null or f: number -> number to transform axis + inverseTransform: null, // if transform is set, this should be the inverse function + min: null, // min. value to show, null means set automatically + max: null, // max. value to show, null means set automatically + autoscaleMargin: null, // margin in % to add if auto-setting min/max + ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks + tickFormatter: null, // fn: number -> string + labelWidth: null, // size of tick labels in pixels + labelHeight: null, + + // mode specific options + tickDecimals: null, // no. of decimals, null means auto + tickSize: null, // number or [number, "unit"] + minTickSize: null, // number or [number, "unit"] + monthNames: null, // list of names of months + timeformat: null, // format string to use + twelveHourClock: false // 12 or 24 time in time mode + }, + yaxis: { + autoscaleMargin: 0.02 + }, + x2axis: { + autoscaleMargin: null + }, + y2axis: { + autoscaleMargin: 0.02 + }, + series: { + points: { + show: false, + radius: 3, + lineWidth: 2, // in pixels + fill: true, + fillColor: "#ffffff" + }, + lines: { + // we don't put in show: false so we can see + // whether lines were actively disabled + lineWidth: 2, // in pixels + fill: false, + fillColor: null, + steps: false + }, + bars: { + show: false, + lineWidth: 2, // in pixels + barWidth: 1, // in units of the x axis + fill: true, + fillColor: null, + align: "left", // or "center" + horizontal: false // when horizontal, left is now top + }, + shadowSize: 3 + }, + grid: { + show: true, + aboveData: false, + color: "#545454", // primary color used for outline and labels + backgroundColor: null, // null for transparent, else color + tickColor: "rgba(0,0,0,0.15)", // color used for the ticks + labelMargin: 5, // in pixels + borderWidth: 2, // in pixels + borderColor: null, // set if different from the grid color + markings: null, // array of ranges or fn: axes -> array of ranges + markingsColor: "#f4f4f4", + markingsLineWidth: 2, + // interactive stuff + clickable: false, + hoverable: false, + autoHighlight: true, // highlight in case mouse is near + mouseActiveRadius: 10 // how far the mouse can be away to activate an item + }, + hooks: {} + }, + canvas = null, // the canvas for the plot itself + overlay = null, // canvas for interactive stuff on top of plot + eventHolder = null, // jQuery object that events should be bound to + ctx = null, octx = null, + axes = { xaxis: {}, yaxis: {}, x2axis: {}, y2axis: {} }, + plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, + canvasWidth = 0, canvasHeight = 0, + plotWidth = 0, plotHeight = 0, + hooks = { + processOptions: [], + processRawData: [], + processDatapoints: [], + draw: [], + bindEvents: [], + drawOverlay: [] + }, + plot = this; + + // public functions + plot.setData = setData; + plot.setupGrid = setupGrid; + plot.draw = draw; + plot.getPlaceholder = function() { return placeholder; }; + plot.getCanvas = function() { return canvas; }; + plot.getPlotOffset = function() { return plotOffset; }; + plot.width = function () { return plotWidth; }; + plot.height = function () { return plotHeight; }; + plot.offset = function () { + var o = eventHolder.offset(); + o.left += plotOffset.left; + o.top += plotOffset.top; + return o; + }; + plot.getData = function() { return series; }; + plot.getAxes = function() { return axes; }; + plot.getOptions = function() { return options; }; + plot.highlight = highlight; + plot.unhighlight = unhighlight; + plot.triggerRedrawOverlay = triggerRedrawOverlay; + plot.pointOffset = function(point) { + return { left: parseInt(axisSpecToRealAxis(point, "xaxis").p2c(+point.x) + plotOffset.left), + top: parseInt(axisSpecToRealAxis(point, "yaxis").p2c(+point.y) + plotOffset.top) }; + }; + + + // public attributes + plot.hooks = hooks; + + // initialize + initPlugins(plot); + parseOptions(options_); + constructCanvas(); + setData(data_); + setupGrid(); + draw(); + bindEvents(); + + + function executeHooks(hook, args) { + args = [plot].concat(args); + for (var i = 0; i < hook.length; ++i) + hook[i].apply(this, args); + } + + function initPlugins() { + for (var i = 0; i < plugins.length; ++i) { + var p = plugins[i]; + p.init(plot); + if (p.options) + $.extend(true, options, p.options); + } + } + + function parseOptions(opts) { + $.extend(true, options, opts); + if (options.grid.borderColor == null) + options.grid.borderColor = options.grid.color; + // backwards compatibility, to be removed in future + if (options.xaxis.noTicks && options.xaxis.ticks == null) + options.xaxis.ticks = options.xaxis.noTicks; + if (options.yaxis.noTicks && options.yaxis.ticks == null) + options.yaxis.ticks = options.yaxis.noTicks; + if (options.grid.coloredAreas) + options.grid.markings = options.grid.coloredAreas; + if (options.grid.coloredAreasColor) + options.grid.markingsColor = options.grid.coloredAreasColor; + if (options.lines) + $.extend(true, options.series.lines, options.lines); + if (options.points) + $.extend(true, options.series.points, options.points); + if (options.bars) + $.extend(true, options.series.bars, options.bars); + if (options.shadowSize) + options.series.shadowSize = options.shadowSize; + + for (var n in hooks) + if (options.hooks[n] && options.hooks[n].length) + hooks[n] = hooks[n].concat(options.hooks[n]); + + executeHooks(hooks.processOptions, [options]); + } + + function setData(d) { + series = parseData(d); + fillInSeriesOptions(); + processData(); + } + + function parseData(d) { + var res = []; + for (var i = 0; i < d.length; ++i) { + var s = $.extend(true, {}, options.series); + + if (d[i].data) { + s.data = d[i].data; // move the data instead of deep-copy + delete d[i].data; + + $.extend(true, s, d[i]); + + d[i].data = s.data; + } + else + s.data = d[i]; + res.push(s); + } + + return res; + } + + function axisSpecToRealAxis(obj, attr) { + var a = obj[attr]; + if (!a || a == 1) + return axes[attr]; + if (typeof a == "number") + return axes[attr.charAt(0) + a + attr.slice(1)]; + return a; // assume it's OK + } + + function fillInSeriesOptions() { + var i; + + // collect what we already got of colors + var neededColors = series.length, + usedColors = [], + assignedColors = []; + for (i = 0; i < series.length; ++i) { + var sc = series[i].color; + if (sc != null) { + --neededColors; + if (typeof sc == "number") + assignedColors.push(sc); + else + usedColors.push($.color.parse(series[i].color)); + } + } + + // we might need to generate more colors if higher indices + // are assigned + for (i = 0; i < assignedColors.length; ++i) { + neededColors = Math.max(neededColors, assignedColors[i] + 1); + } + + // produce colors as needed + var colors = [], variation = 0; + i = 0; + while (colors.length < neededColors) { + var c; + if (options.colors.length == i) // check degenerate case + c = $.color.make(100, 100, 100); + else + c = $.color.parse(options.colors[i]); + + // vary color if needed + var sign = variation % 2 == 1 ? -1 : 1; + c.scale('rgb', 1 + sign * Math.ceil(variation / 2) * 0.2) + + // FIXME: if we're getting to close to something else, + // we should probably skip this one + colors.push(c); + + ++i; + if (i >= options.colors.length) { + i = 0; + ++variation; + } + } + + // fill in the options + var colori = 0, s; + for (i = 0; i < series.length; ++i) { + s = series[i]; + + // assign colors + if (s.color == null) { + s.color = colors[colori].toString(); + ++colori; + } + else if (typeof s.color == "number") + s.color = colors[s.color].toString(); + + // turn on lines automatically in case nothing is set + if (s.lines.show == null) { + var v, show = true; + for (v in s) + if (s[v].show) { + show = false; + break; + } + if (show) + s.lines.show = true; + } + + // setup axes + s.xaxis = axisSpecToRealAxis(s, "xaxis"); + s.yaxis = axisSpecToRealAxis(s, "yaxis"); + } + } + + function processData() { + var topSentry = Number.POSITIVE_INFINITY, + bottomSentry = Number.NEGATIVE_INFINITY, + i, j, k, m, length, + s, points, ps, x, y, axis, val, f, p; + + for (axis in axes) { + axes[axis].datamin = topSentry; + axes[axis].datamax = bottomSentry; + axes[axis].used = false; + } + + function updateAxis(axis, min, max) { + if (min < axis.datamin) + axis.datamin = min; + if (max > axis.datamax) + axis.datamax = max; + } + + for (i = 0; i < series.length; ++i) { + s = series[i]; + s.datapoints = { points: [] }; + + executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]); + } + + // first pass: clean and copy data + for (i = 0; i < series.length; ++i) { + s = series[i]; + + var data = s.data, format = s.datapoints.format; + + if (!format) { + format = []; + // find out how to copy + format.push({ x: true, number: true, required: true }); + format.push({ y: true, number: true, required: true }); + + if (s.bars.show) + format.push({ y: true, number: true, required: false, defaultValue: 0 }); + + s.datapoints.format = format; + } + + if (s.datapoints.pointsize != null) + continue; // already filled in + + if (s.datapoints.pointsize == null) + s.datapoints.pointsize = format.length; + + ps = s.datapoints.pointsize; + points = s.datapoints.points; + + insertSteps = s.lines.show && s.lines.steps; + s.xaxis.used = s.yaxis.used = true; + + for (j = k = 0; j < data.length; ++j, k += ps) { + p = data[j]; + + var nullify = p == null; + if (!nullify) { + for (m = 0; m < ps; ++m) { + val = p[m]; + f = format[m]; + + if (f) { + if (f.number && val != null) { + val = +val; // convert to number + if (isNaN(val)) + val = null; + } + + if (val == null) { + if (f.required) + nullify = true; + + if (f.defaultValue != null) + val = f.defaultValue; + } + } + + points[k + m] = val; + } + } + + if (nullify) { + for (m = 0; m < ps; ++m) { + val = points[k + m]; + if (val != null) { + f = format[m]; + // extract min/max info + if (f.x) + updateAxis(s.xaxis, val, val); + if (f.y) + updateAxis(s.yaxis, val, val); + } + points[k + m] = null; + } + } + else { + // a little bit of line specific stuff that + // perhaps shouldn't be here, but lacking + // better means... + if (insertSteps && k > 0 + && points[k - ps] != null + && points[k - ps] != points[k] + && points[k - ps + 1] != points[k + 1]) { + // copy the point to make room for a middle point + for (m = 0; m < ps; ++m) + points[k + ps + m] = points[k + m]; + + // middle point has same y + points[k + 1] = points[k - ps + 1]; + + // we've added a point, better reflect that + k += ps; + } + } + } + } + + // give the hooks a chance to run + for (i = 0; i < series.length; ++i) { + s = series[i]; + + executeHooks(hooks.processDatapoints, [ s, s.datapoints]); + } + + // second pass: find datamax/datamin for auto-scaling + for (i = 0; i < series.length; ++i) { + s = series[i]; + points = s.datapoints.points, + ps = s.datapoints.pointsize; + + var xmin = topSentry, ymin = topSentry, + xmax = bottomSentry, ymax = bottomSentry; + + for (j = 0; j < points.length; j += ps) { + if (points[j] == null) + continue; + + for (m = 0; m < ps; ++m) { + val = points[j + m]; + f = format[m]; + if (!f) + continue; + + if (f.x) { + if (val < xmin) + xmin = val; + if (val > xmax) + xmax = val; + } + if (f.y) { + if (val < ymin) + ymin = val; + if (val > ymax) + ymax = val; + } + } + } + + if (s.bars.show) { + // make sure we got room for the bar on the dancing floor + var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2; + if (s.bars.horizontal) { + ymin += delta; + ymax += delta + s.bars.barWidth; + } + else { + xmin += delta; + xmax += delta + s.bars.barWidth; + } + } + + updateAxis(s.xaxis, xmin, xmax); + updateAxis(s.yaxis, ymin, ymax); + } + + for (axis in axes) { + if (axes[axis].datamin == topSentry) + axes[axis].datamin = null; + if (axes[axis].datamax == bottomSentry) + axes[axis].datamax = null; + } + } + + function constructCanvas() { + function makeCanvas(width, height) { + var c = document.createElement('canvas'); + c.width = width; + c.height = height; + if ($.browser.msie) // excanvas hack + c = window.G_vmlCanvasManager.initElement(c); + return c; + } + + canvasWidth = placeholder.width(); + canvasHeight = placeholder.height(); + placeholder.html(""); // clear placeholder + if (placeholder.css("position") == 'static') + placeholder.css("position", "relative"); // for positioning labels and overlay + + if (canvasWidth <= 0 || canvasHeight <= 0) + throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight; + + if ($.browser.msie) // excanvas hack + window.G_vmlCanvasManager.init_(document); // make sure everything is setup + + // the canvas + canvas = $(makeCanvas(canvasWidth, canvasHeight)).appendTo(placeholder).get(0); + ctx = canvas.getContext("2d"); + + // overlay canvas for interactive features + overlay = $(makeCanvas(canvasWidth, canvasHeight)).css({ position: 'absolute', left: 0, top: 0 }).appendTo(placeholder).get(0); + octx = overlay.getContext("2d"); + octx.stroke(); + } + + function bindEvents() { + // we include the canvas in the event holder too, because IE 7 + // sometimes has trouble with the stacking order + eventHolder = $([overlay, canvas]); + + // bind events + if (options.grid.hoverable) + eventHolder.mousemove(onMouseMove); + + if (options.grid.clickable) + eventHolder.click(onClick); + + executeHooks(hooks.bindEvents, [eventHolder]); + } + + function setupGrid() { + function setTransformationHelpers(axis, o) { + function identity(x) { return x; } + + var s, m, t = o.transform || identity, + it = o.inverseTransform; + + // add transformation helpers + if (axis == axes.xaxis || axis == axes.x2axis) { + // precompute how much the axis is scaling a point + // in canvas space + s = axis.scale = plotWidth / (t(axis.max) - t(axis.min)); + m = t(axis.min); + + // data point to canvas coordinate + if (t == identity) // slight optimization + axis.p2c = function (p) { return (p - m) * s; }; + else + axis.p2c = function (p) { return (t(p) - m) * s; }; + // canvas coordinate to data point + if (!it) + axis.c2p = function (c) { return m + c / s; }; + else + axis.c2p = function (c) { return it(m + c / s); }; + } + else { + s = axis.scale = plotHeight / (t(axis.max) - t(axis.min)); + m = t(axis.max); + + if (t == identity) + axis.p2c = function (p) { return (m - p) * s; }; + else + axis.p2c = function (p) { return (m - t(p)) * s; }; + if (!it) + axis.c2p = function (c) { return m - c / s; }; + else + axis.c2p = function (c) { return it(m - c / s); }; + } + } + + function measureLabels(axis, axisOptions) { + var i, labels = [], l; + + axis.labelWidth = axisOptions.labelWidth; + axis.labelHeight = axisOptions.labelHeight; + + if (axis == axes.xaxis || axis == axes.x2axis) { + // to avoid measuring the widths of the labels, we + // construct fixed-size boxes and put the labels inside + // them, we don't need the exact figures and the + // fixed-size box content is easy to center + if (axis.labelWidth == null) + axis.labelWidth = canvasWidth / (axis.ticks.length > 0 ? axis.ticks.length : 1); + + // measure x label heights + if (axis.labelHeight == null) { + labels = []; + for (i = 0; i < axis.ticks.length; ++i) { + l = axis.ticks[i].label; + if (l) + labels.push('
' + l + '
'); + } + + if (labels.length > 0) { + var dummyDiv = $('
' + + labels.join("") + '
').appendTo(placeholder); + axis.labelHeight = dummyDiv.height(); + dummyDiv.remove(); + } + } + } + else if (axis.labelWidth == null || axis.labelHeight == null) { + // calculate y label dimensions + for (i = 0; i < axis.ticks.length; ++i) { + l = axis.ticks[i].label; + if (l) + labels.push('
' + l + '
'); + } + + if (labels.length > 0) { + var dummyDiv = $('
' + + labels.join("") + '
').appendTo(placeholder); + if (axis.labelWidth == null) + axis.labelWidth = dummyDiv.width(); + if (axis.labelHeight == null) + axis.labelHeight = dummyDiv.find("div").height(); + dummyDiv.remove(); + } + + } + + if (axis.labelWidth == null) + axis.labelWidth = 0; + if (axis.labelHeight == null) + axis.labelHeight = 0; + } + + function setGridSpacing() { + // get the most space needed around the grid for things + // that may stick out + var maxOutset = options.grid.borderWidth; + for (i = 0; i < series.length; ++i) + maxOutset = Math.max(maxOutset, 2 * (series[i].points.radius + series[i].points.lineWidth/2)); + + plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = maxOutset; + + var margin = options.grid.labelMargin + options.grid.borderWidth; + + if (axes.xaxis.labelHeight > 0) + plotOffset.bottom = Math.max(maxOutset, axes.xaxis.labelHeight + margin); + if (axes.yaxis.labelWidth > 0) + plotOffset.left = Math.max(maxOutset, axes.yaxis.labelWidth + margin); + if (axes.x2axis.labelHeight > 0) + plotOffset.top = Math.max(maxOutset, axes.x2axis.labelHeight + margin); + if (axes.y2axis.labelWidth > 0) + plotOffset.right = Math.max(maxOutset, axes.y2axis.labelWidth + margin); + + plotWidth = canvasWidth - plotOffset.left - plotOffset.right; + plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top; + } + + var axis; + for (axis in axes) + setRange(axes[axis], options[axis]); + + if (options.grid.show) { + for (axis in axes) { + prepareTickGeneration(axes[axis], options[axis]); + setTicks(axes[axis], options[axis]); + measureLabels(axes[axis], options[axis]); + } + + setGridSpacing(); + } + else { + plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0; + plotWidth = canvasWidth; + plotHeight = canvasHeight; + } + + for (axis in axes) + setTransformationHelpers(axes[axis], options[axis]); + + if (options.grid.show) + insertLabels(); + + insertLegend(); + } + + function setRange(axis, axisOptions) { + var min = +(axisOptions.min != null ? axisOptions.min : axis.datamin), + max = +(axisOptions.max != null ? axisOptions.max : axis.datamax), + delta = max - min; + + if (delta == 0.0) { + // degenerate case + var widen = max == 0 ? 1 : 0.01; + + if (axisOptions.min == null) + min -= widen; + // alway widen max if we couldn't widen min to ensure we + // don't fall into min == max which doesn't work + if (axisOptions.max == null || axisOptions.min != null) + max += widen; + } + else { + // consider autoscaling + var margin = axisOptions.autoscaleMargin; + if (margin != null) { + if (axisOptions.min == null) { + min -= delta * margin; + // make sure we don't go below zero if all values + // are positive + if (min < 0 && axis.datamin != null && axis.datamin >= 0) + min = 0; + } + if (axisOptions.max == null) { + max += delta * margin; + if (max > 0 && axis.datamax != null && axis.datamax <= 0) + max = 0; + } + } + } + axis.min = min; + axis.max = max; + } + + function prepareTickGeneration(axis, axisOptions) { + // estimate number of ticks + var noTicks; + if (typeof axisOptions.ticks == "number" && axisOptions.ticks > 0) + noTicks = axisOptions.ticks; + else if (axis == axes.xaxis || axis == axes.x2axis) + // heuristic based on the model a*sqrt(x) fitted to + // some reasonable data points + noTicks = 0.3 * Math.sqrt(canvasWidth); + else + noTicks = 0.3 * Math.sqrt(canvasHeight); + + var delta = (axis.max - axis.min) / noTicks, + size, generator, unit, formatter, i, magn, norm; + + if (axisOptions.mode == "time") { + // pretty handling of time + + // map of app. size of time units in milliseconds + var timeUnitSize = { + "second": 1000, + "minute": 60 * 1000, + "hour": 60 * 60 * 1000, + "day": 24 * 60 * 60 * 1000, + "month": 30 * 24 * 60 * 60 * 1000, + "year": 365.2425 * 24 * 60 * 60 * 1000 + }; + + + // the allowed tick sizes, after 1 year we use + // an integer algorithm + var spec = [ + [1, "second"], [2, "second"], [5, "second"], [10, "second"], + [30, "second"], + [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], + [30, "minute"], + [1, "hour"], [2, "hour"], [4, "hour"], + [8, "hour"], [12, "hour"], + [1, "day"], [2, "day"], [3, "day"], + [0.25, "month"], [0.5, "month"], [1, "month"], + [2, "month"], [3, "month"], [6, "month"], + [1, "year"] + ]; + + var minSize = 0; + if (axisOptions.minTickSize != null) { + if (typeof axisOptions.tickSize == "number") + minSize = axisOptions.tickSize; + else + minSize = axisOptions.minTickSize[0] * timeUnitSize[axisOptions.minTickSize[1]]; + } + + for (i = 0; i < spec.length - 1; ++i) + if (delta < (spec[i][0] * timeUnitSize[spec[i][1]] + + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 + && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) + break; + size = spec[i][0]; + unit = spec[i][1]; + + // special-case the possibility of several years + if (unit == "year") { + magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10)); + norm = (delta / timeUnitSize.year) / magn; + if (norm < 1.5) + size = 1; + else if (norm < 3) + size = 2; + else if (norm < 7.5) + size = 5; + else + size = 10; + + size *= magn; + } + + if (axisOptions.tickSize) { + size = axisOptions.tickSize[0]; + unit = axisOptions.tickSize[1]; + } + + generator = function(axis) { + var ticks = [], + tickSize = axis.tickSize[0], unit = axis.tickSize[1], + d = new Date(axis.min); + + var step = tickSize * timeUnitSize[unit]; + + if (unit == "second") + d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize)); + if (unit == "minute") + d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize)); + if (unit == "hour") + d.setUTCHours(floorInBase(d.getUTCHours(), tickSize)); + if (unit == "month") + d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize)); + if (unit == "year") + d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize)); + + // reset smaller components + d.setUTCMilliseconds(0); + if (step >= timeUnitSize.minute) + d.setUTCSeconds(0); + if (step >= timeUnitSize.hour) + d.setUTCMinutes(0); + if (step >= timeUnitSize.day) + d.setUTCHours(0); + if (step >= timeUnitSize.day * 4) + d.setUTCDate(1); + if (step >= timeUnitSize.year) + d.setUTCMonth(0); + + + var carry = 0, v = Number.NaN, prev; + do { + prev = v; + v = d.getTime(); + ticks.push({ v: v, label: axis.tickFormatter(v, axis) }); + if (unit == "month") { + if (tickSize < 1) { + // a bit complicated - we'll divide the month + // up but we need to take care of fractions + // so we don't end up in the middle of a day + d.setUTCDate(1); + var start = d.getTime(); + d.setUTCMonth(d.getUTCMonth() + 1); + var end = d.getTime(); + d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); + carry = d.getUTCHours(); + d.setUTCHours(0); + } + else + d.setUTCMonth(d.getUTCMonth() + tickSize); + } + else if (unit == "year") { + d.setUTCFullYear(d.getUTCFullYear() + tickSize); + } + else + d.setTime(v + step); + } while (v < axis.max && v != prev); + + return ticks; + }; + + formatter = function (v, axis) { + var d = new Date(v); + + // first check global format + if (axisOptions.timeformat != null) + return $.plot.formatDate(d, axisOptions.timeformat, axisOptions.monthNames); + + var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; + var span = axis.max - axis.min; + var suffix = (axisOptions.twelveHourClock) ? " %p" : ""; + + if (t < timeUnitSize.minute) + fmt = "%h:%M:%S" + suffix; + else if (t < timeUnitSize.day) { + if (span < 2 * timeUnitSize.day) + fmt = "%h:%M" + suffix; + else + fmt = "%b %d %h:%M" + suffix; + } + else if (t < timeUnitSize.month) + fmt = "%b %d"; + else if (t < timeUnitSize.year) { + if (span < timeUnitSize.year) + fmt = "%b"; + else + fmt = "%b %y"; + } + else + fmt = "%y"; + + return $.plot.formatDate(d, fmt, axisOptions.monthNames); + }; + } + else { + // pretty rounding of base-10 numbers + var maxDec = axisOptions.tickDecimals; + var dec = -Math.floor(Math.log(delta) / Math.LN10); + if (maxDec != null && dec > maxDec) + dec = maxDec; + + magn = Math.pow(10, -dec); + norm = delta / magn; // norm is between 1.0 and 10.0 + + if (norm < 1.5) + size = 1; + else if (norm < 3) { + size = 2; + // special case for 2.5, requires an extra decimal + if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { + size = 2.5; + ++dec; + } + } + else if (norm < 7.5) + size = 5; + else + size = 10; + + size *= magn; + + if (axisOptions.minTickSize != null && size < axisOptions.minTickSize) + size = axisOptions.minTickSize; + + if (axisOptions.tickSize != null) + size = axisOptions.tickSize; + + axis.tickDecimals = Math.max(0, (maxDec != null) ? maxDec : dec); + + generator = function (axis) { + var ticks = []; + + // spew out all possible ticks + var start = floorInBase(axis.min, axis.tickSize), + i = 0, v = Number.NaN, prev; + do { + prev = v; + v = start + i * axis.tickSize; + ticks.push({ v: v, label: axis.tickFormatter(v, axis) }); + ++i; + } while (v < axis.max && v != prev); + return ticks; + }; + + formatter = function (v, axis) { + return v.toFixed(axis.tickDecimals); + }; + } + + axis.tickSize = unit ? [size, unit] : size; + axis.tickGenerator = generator; + if ($.isFunction(axisOptions.tickFormatter)) + axis.tickFormatter = function (v, axis) { return "" + axisOptions.tickFormatter(v, axis); }; + else + axis.tickFormatter = formatter; + } + + function setTicks(axis, axisOptions) { + axis.ticks = []; + + if (!axis.used) + return; + + if (axisOptions.ticks == null) + axis.ticks = axis.tickGenerator(axis); + else if (typeof axisOptions.ticks == "number") { + if (axisOptions.ticks > 0) + axis.ticks = axis.tickGenerator(axis); + } + else if (axisOptions.ticks) { + var ticks = axisOptions.ticks; + + if ($.isFunction(ticks)) + // generate the ticks + ticks = ticks({ min: axis.min, max: axis.max }); + + // clean up the user-supplied ticks, copy them over + var i, v; + for (i = 0; i < ticks.length; ++i) { + var label = null; + var t = ticks[i]; + if (typeof t == "object") { + v = t[0]; + if (t.length > 1) + label = t[1]; + } + else + v = t; + if (label == null) + label = axis.tickFormatter(v, axis); + axis.ticks[i] = { v: v, label: label }; + } + } + + if (axisOptions.autoscaleMargin != null && axis.ticks.length > 0) { + // snap to ticks + if (axisOptions.min == null) + axis.min = Math.min(axis.min, axis.ticks[0].v); + if (axisOptions.max == null && axis.ticks.length > 1) + axis.max = Math.max(axis.max, axis.ticks[axis.ticks.length - 1].v); + } + } + + function draw() { + ctx.clearRect(0, 0, canvasWidth, canvasHeight); + + var grid = options.grid; + + if (grid.show && !grid.aboveData) + drawGrid(); + + for (var i = 0; i < series.length; ++i) + drawSeries(series[i]); + + executeHooks(hooks.draw, [ctx]); + + if (grid.show && grid.aboveData) + drawGrid(); + } + + function extractRange(ranges, coord) { + var firstAxis = coord + "axis", + secondaryAxis = coord + "2axis", + axis, from, to, reverse; + + if (ranges[firstAxis]) { + axis = axes[firstAxis]; + from = ranges[firstAxis].from; + to = ranges[firstAxis].to; + } + else if (ranges[secondaryAxis]) { + axis = axes[secondaryAxis]; + from = ranges[secondaryAxis].from; + to = ranges[secondaryAxis].to; + } + else { + // backwards-compat stuff - to be removed in future + axis = axes[firstAxis]; + from = ranges[coord + "1"]; + to = ranges[coord + "2"]; + } + + // auto-reverse as an added bonus + if (from != null && to != null && from > to) + return { from: to, to: from, axis: axis }; + + return { from: from, to: to, axis: axis }; + } + + function drawGrid() { + var i; + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + // draw background, if any + if (options.grid.backgroundColor) { + ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); + ctx.fillRect(0, 0, plotWidth, plotHeight); + } + + // draw markings + var markings = options.grid.markings; + if (markings) { + if ($.isFunction(markings)) + // xmin etc. are backwards-compatible, to be removed in future + markings = markings({ xmin: axes.xaxis.min, xmax: axes.xaxis.max, ymin: axes.yaxis.min, ymax: axes.yaxis.max, xaxis: axes.xaxis, yaxis: axes.yaxis, x2axis: axes.x2axis, y2axis: axes.y2axis }); + + for (i = 0; i < markings.length; ++i) { + var m = markings[i], + xrange = extractRange(m, "x"), + yrange = extractRange(m, "y"); + + // fill in missing + if (xrange.from == null) + xrange.from = xrange.axis.min; + if (xrange.to == null) + xrange.to = xrange.axis.max; + if (yrange.from == null) + yrange.from = yrange.axis.min; + if (yrange.to == null) + yrange.to = yrange.axis.max; + + // clip + if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || + yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) + continue; + + xrange.from = Math.max(xrange.from, xrange.axis.min); + xrange.to = Math.min(xrange.to, xrange.axis.max); + yrange.from = Math.max(yrange.from, yrange.axis.min); + yrange.to = Math.min(yrange.to, yrange.axis.max); + + if (xrange.from == xrange.to && yrange.from == yrange.to) + continue; + + // then draw + xrange.from = xrange.axis.p2c(xrange.from); + xrange.to = xrange.axis.p2c(xrange.to); + yrange.from = yrange.axis.p2c(yrange.from); + yrange.to = yrange.axis.p2c(yrange.to); + + if (xrange.from == xrange.to || yrange.from == yrange.to) { + // draw line + ctx.beginPath(); + ctx.strokeStyle = m.color || options.grid.markingsColor; + ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth; + //ctx.moveTo(Math.floor(xrange.from), yrange.from); + //ctx.lineTo(Math.floor(xrange.to), yrange.to); + ctx.moveTo(xrange.from, yrange.from); + ctx.lineTo(xrange.to, yrange.to); + ctx.stroke(); + } + else { + // fill area + ctx.fillStyle = m.color || options.grid.markingsColor; + ctx.fillRect(xrange.from, yrange.to, + xrange.to - xrange.from, + yrange.from - yrange.to); + } + } + } + + // draw the inner grid + ctx.lineWidth = 1; + ctx.strokeStyle = options.grid.tickColor; + ctx.beginPath(); + var v, axis = axes.xaxis; + for (i = 0; i < axis.ticks.length; ++i) { + v = axis.ticks[i].v; + if (v <= axis.min || v >= axes.xaxis.max) + continue; // skip those lying on the axes + + ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 0); + ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, plotHeight); + } + + axis = axes.yaxis; + for (i = 0; i < axis.ticks.length; ++i) { + v = axis.ticks[i].v; + if (v <= axis.min || v >= axis.max) + continue; + + ctx.moveTo(0, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); + ctx.lineTo(plotWidth, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); + } + + axis = axes.x2axis; + for (i = 0; i < axis.ticks.length; ++i) { + v = axis.ticks[i].v; + if (v <= axis.min || v >= axis.max) + continue; + + ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, -5); + ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 5); + } + + axis = axes.y2axis; + for (i = 0; i < axis.ticks.length; ++i) { + v = axis.ticks[i].v; + if (v <= axis.min || v >= axis.max) + continue; + + ctx.moveTo(plotWidth-5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); + ctx.lineTo(plotWidth+5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); + } + + ctx.stroke(); + + if (options.grid.borderWidth) { + // draw border + var bw = options.grid.borderWidth; + ctx.lineWidth = bw; + ctx.strokeStyle = options.grid.borderColor; + ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); + } + + ctx.restore(); + } + + function insertLabels() { + placeholder.find(".tickLabels").remove(); + + var html = ['
']; + + function addLabels(axis, labelGenerator) { + for (var i = 0; i < axis.ticks.length; ++i) { + var tick = axis.ticks[i]; + if (!tick.label || tick.v < axis.min || tick.v > axis.max) + continue; + html.push(labelGenerator(tick, axis)); + } + } + + var margin = options.grid.labelMargin + options.grid.borderWidth; + + addLabels(axes.xaxis, function (tick, axis) { + return '
' + tick.label + "
"; + }); + + + addLabels(axes.yaxis, function (tick, axis) { + return '
' + tick.label + "
"; + }); + + addLabels(axes.x2axis, function (tick, axis) { + return '
' + tick.label + "
"; + }); + + addLabels(axes.y2axis, function (tick, axis) { + return '
' + tick.label + "
"; + }); + + html.push('
'); + + placeholder.append(html.join("")); + } + + function drawSeries(series) { + if (series.lines.show) + drawSeriesLines(series); + if (series.bars.show) + drawSeriesBars(series); + if (series.points.show) + drawSeriesPoints(series); + } + + function drawSeriesLines(series) { + function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { + var points = datapoints.points, + ps = datapoints.pointsize, + prevx = null, prevy = null; + + ctx.beginPath(); + for (var i = ps; i < points.length; i += ps) { + var x1 = points[i - ps], y1 = points[i - ps + 1], + x2 = points[i], y2 = points[i + 1]; + + if (x1 == null || x2 == null) + continue; + + // clip with ymin + if (y1 <= y2 && y1 < axisy.min) { + if (y2 < axisy.min) + continue; // line segment is outside + // compute new intersection point + x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.min; + } + else if (y2 <= y1 && y2 < axisy.min) { + if (y1 < axisy.min) + continue; + x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.min; + } + + // clip with ymax + if (y1 >= y2 && y1 > axisy.max) { + if (y2 > axisy.max) + continue; + x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.max; + } + else if (y2 >= y1 && y2 > axisy.max) { + if (y1 > axisy.max) + continue; + x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.max; + } + + // clip with xmin + if (x1 <= x2 && x1 < axisx.min) { + if (x2 < axisx.min) + continue; + y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.min; + } + else if (x2 <= x1 && x2 < axisx.min) { + if (x1 < axisx.min) + continue; + y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.min; + } + + // clip with xmax + if (x1 >= x2 && x1 > axisx.max) { + if (x2 > axisx.max) + continue; + y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.max; + } + else if (x2 >= x1 && x2 > axisx.max) { + if (x1 > axisx.max) + continue; + y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.max; + } + + if (x1 != prevx || y1 != prevy) + ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); + + prevx = x2; + prevy = y2; + ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); + } + ctx.stroke(); + } + + function plotLineArea(datapoints, axisx, axisy) { + var points = datapoints.points, + ps = datapoints.pointsize, + bottom = Math.min(Math.max(0, axisy.min), axisy.max), + top, lastX = 0, areaOpen = false; + + for (var i = ps; i < points.length; i += ps) { + var x1 = points[i - ps], y1 = points[i - ps + 1], + x2 = points[i], y2 = points[i + 1]; + + if (areaOpen && x1 != null && x2 == null) { + // close area + ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom)); + ctx.fill(); + areaOpen = false; + continue; + } + + if (x1 == null || x2 == null) + continue; + + // clip x values + + // clip with xmin + if (x1 <= x2 && x1 < axisx.min) { + if (x2 < axisx.min) + continue; + y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.min; + } + else if (x2 <= x1 && x2 < axisx.min) { + if (x1 < axisx.min) + continue; + y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.min; + } + + // clip with xmax + if (x1 >= x2 && x1 > axisx.max) { + if (x2 > axisx.max) + continue; + y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.max; + } + else if (x2 >= x1 && x2 > axisx.max) { + if (x1 > axisx.max) + continue; + y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.max; + } + + if (!areaOpen) { + // open area + ctx.beginPath(); + ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); + areaOpen = true; + } + + // now first check the case where both is outside + if (y1 >= axisy.max && y2 >= axisy.max) { + ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); + lastX = x2; + continue; + } + else if (y1 <= axisy.min && y2 <= axisy.min) { + ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); + lastX = x2; + continue; + } + + // else it's a bit more complicated, there might + // be two rectangles and two triangles we need to fill + // in; to find these keep track of the current x values + var x1old = x1, x2old = x2; + + // and clip the y values, without shortcutting + + // clip with ymin + if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { + x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.min; + } + else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { + x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.min; + } + + // clip with ymax + if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { + x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.max; + } + else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { + x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.max; + } + + + // if the x value was changed we got a rectangle + // to fill + if (x1 != x1old) { + if (y1 <= axisy.min) + top = axisy.min; + else + top = axisy.max; + + ctx.lineTo(axisx.p2c(x1old), axisy.p2c(top)); + ctx.lineTo(axisx.p2c(x1), axisy.p2c(top)); + } + + // fill the triangles + ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); + + // fill the other rectangle if it's there + if (x2 != x2old) { + if (y2 <= axisy.min) + top = axisy.min; + else + top = axisy.max; + + ctx.lineTo(axisx.p2c(x2), axisy.p2c(top)); + ctx.lineTo(axisx.p2c(x2old), axisy.p2c(top)); + } + + lastX = Math.max(x2, x2old); + } + + if (areaOpen) { + ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom)); + ctx.fill(); + } + } + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + ctx.lineJoin = "round"; + + var lw = series.lines.lineWidth, + sw = series.shadowSize; + // FIXME: consider another form of shadow when filling is turned on + if (lw > 0 && sw > 0) { + // draw shadow as a thick and thin line with transparency + ctx.lineWidth = sw; + ctx.strokeStyle = "rgba(0,0,0,0.1)"; + // position shadow at angle from the mid of line + var angle = Math.PI/18; + plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis); + ctx.lineWidth = sw/2; + plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis); + } + + ctx.lineWidth = lw; + ctx.strokeStyle = series.color; + var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); + if (fillStyle) { + ctx.fillStyle = fillStyle; + plotLineArea(series.datapoints, series.xaxis, series.yaxis); + } + + if (lw > 0) + plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis); + ctx.restore(); + } + + function drawSeriesPoints(series) { + function plotPoints(datapoints, radius, fillStyle, offset, circumference, axisx, axisy) { + var points = datapoints.points, ps = datapoints.pointsize; + + for (var i = 0; i < points.length; i += ps) { + var x = points[i], y = points[i + 1]; + if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) + continue; + + ctx.beginPath(); + ctx.arc(axisx.p2c(x), axisy.p2c(y) + offset, radius, 0, circumference, false); + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + ctx.stroke(); + } + } + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + var lw = series.lines.lineWidth, + sw = series.shadowSize, + radius = series.points.radius; + if (lw > 0 && sw > 0) { + // draw shadow in two steps + var w = sw / 2; + ctx.lineWidth = w; + ctx.strokeStyle = "rgba(0,0,0,0.1)"; + plotPoints(series.datapoints, radius, null, w + w/2, Math.PI, + series.xaxis, series.yaxis); + + ctx.strokeStyle = "rgba(0,0,0,0.2)"; + plotPoints(series.datapoints, radius, null, w/2, Math.PI, + series.xaxis, series.yaxis); + } + + ctx.lineWidth = lw; + ctx.strokeStyle = series.color; + plotPoints(series.datapoints, radius, + getFillStyle(series.points, series.color), 0, 2 * Math.PI, + series.xaxis, series.yaxis); + ctx.restore(); + } + + function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal) { + var left, right, bottom, top, + drawLeft, drawRight, drawTop, drawBottom, + tmp; + + if (horizontal) { + drawBottom = drawRight = drawTop = true; + drawLeft = false; + left = b; + right = x; + top = y + barLeft; + bottom = y + barRight; + + // account for negative bars + if (right < left) { + tmp = right; + right = left; + left = tmp; + drawLeft = true; + drawRight = false; + } + } + else { + drawLeft = drawRight = drawTop = true; + drawBottom = false; + left = x + barLeft; + right = x + barRight; + bottom = b; + top = y; + + // account for negative bars + if (top < bottom) { + tmp = top; + top = bottom; + bottom = tmp; + drawBottom = true; + drawTop = false; + } + } + + // clip + if (right < axisx.min || left > axisx.max || + top < axisy.min || bottom > axisy.max) + return; + + if (left < axisx.min) { + left = axisx.min; + drawLeft = false; + } + + if (right > axisx.max) { + right = axisx.max; + drawRight = false; + } + + if (bottom < axisy.min) { + bottom = axisy.min; + drawBottom = false; + } + + if (top > axisy.max) { + top = axisy.max; + drawTop = false; + } + + left = axisx.p2c(left); + bottom = axisy.p2c(bottom); + right = axisx.p2c(right); + top = axisy.p2c(top); + + // fill the bar + if (fillStyleCallback) { + c.beginPath(); + c.moveTo(left, bottom); + c.lineTo(left, top); + c.lineTo(right, top); + c.lineTo(right, bottom); + c.fillStyle = fillStyleCallback(bottom, top); + c.fill(); + } + + // draw outline + if (drawLeft || drawRight || drawTop || drawBottom) { + c.beginPath(); + + // FIXME: inline moveTo is buggy with excanvas + c.moveTo(left, bottom + offset); + if (drawLeft) + c.lineTo(left, top + offset); + else + c.moveTo(left, top + offset); + if (drawTop) + c.lineTo(right, top + offset); + else + c.moveTo(right, top + offset); + if (drawRight) + c.lineTo(right, bottom + offset); + else + c.moveTo(right, bottom + offset); + if (drawBottom) + c.lineTo(left, bottom + offset); + else + c.moveTo(left, bottom + offset); + c.stroke(); + } + } + + function drawSeriesBars(series) { + function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) { + var points = datapoints.points, ps = datapoints.pointsize; + + for (var i = 0; i < points.length; i += ps) { + if (points[i] == null) + continue; + drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal); + } + } + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + // FIXME: figure out a way to add shadows (for instance along the right edge) + ctx.lineWidth = series.bars.lineWidth; + ctx.strokeStyle = series.color; + var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; + var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; + plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis); + ctx.restore(); + } + + function getFillStyle(filloptions, seriesColor, bottom, top) { + var fill = filloptions.fill; + if (!fill) + return null; + + if (filloptions.fillColor) + return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); + + var c = $.color.parse(seriesColor); + c.a = typeof fill == "number" ? fill : 0.4; + c.normalize(); + return c.toString(); + } + + function insertLegend() { + placeholder.find(".legend").remove(); + + if (!options.legend.show) + return; + + var fragments = [], rowStarted = false, + lf = options.legend.labelFormatter, s, label; + for (i = 0; i < series.length; ++i) { + s = series[i]; + label = s.label; + if (!label) + continue; + + if (i % options.legend.noColumns == 0) { + if (rowStarted) + fragments.push(''); + fragments.push(''); + rowStarted = true; + } + + if (lf) + label = lf(label, s); + + fragments.push( + '
' + + '' + label + ''); + } + if (rowStarted) + fragments.push(''); + + if (fragments.length == 0) + return; + + var table = '' + fragments.join("") + '
'; + if (options.legend.container != null) + $(options.legend.container).html(table); + else { + var pos = "", + p = options.legend.position, + m = options.legend.margin; + if (m[0] == null) + m = [m, m]; + if (p.charAt(0) == "n") + pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; + else if (p.charAt(0) == "s") + pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; + if (p.charAt(1) == "e") + pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; + else if (p.charAt(1) == "w") + pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; + var legend = $('
' + table.replace('style="', 'style="position:absolute;' + pos +';') + '
').appendTo(placeholder); + if (options.legend.backgroundOpacity != 0.0) { + // put in the transparent background + // separately to avoid blended labels and + // label boxes + var c = options.legend.backgroundColor; + if (c == null) { + c = options.grid.backgroundColor; + if (c && typeof c == "string") + c = $.color.parse(c); + else + c = $.color.extract(legend, 'background-color'); + c.a = 1; + c = c.toString(); + } + var div = legend.children(); + $('
').prependTo(legend).css('opacity', options.legend.backgroundOpacity); + } + } + } + + + // interactive features + + var highlights = [], + redrawTimeout = null; + + // returns the data item the mouse is over, or null if none is found + function findNearbyItem(mouseX, mouseY, seriesFilter) { + var maxDistance = options.grid.mouseActiveRadius, + smallestDistance = maxDistance * maxDistance + 1, + item = null, foundPoint = false, i, j; + + for (i = 0; i < series.length; ++i) { + if (!seriesFilter(series[i])) + continue; + + var s = series[i], + axisx = s.xaxis, + axisy = s.yaxis, + points = s.datapoints.points, + ps = s.datapoints.pointsize, + mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster + my = axisy.c2p(mouseY), + maxx = maxDistance / axisx.scale, + maxy = maxDistance / axisy.scale; + + if (s.lines.show || s.points.show) { + for (j = 0; j < points.length; j += ps) { + var x = points[j], y = points[j + 1]; + if (x == null) + continue; + + // For points and lines, the cursor must be within a + // certain distance to the data point + if (x - mx > maxx || x - mx < -maxx || + y - my > maxy || y - my < -maxy) + continue; + + // We have to calculate distances in pixels, not in + // data units, because the scales of the axes may be different + var dx = Math.abs(axisx.p2c(x) - mouseX), + dy = Math.abs(axisy.p2c(y) - mouseY), + dist = dx * dx + dy * dy; // we save the sqrt + + // use <= to ensure last point takes precedence + // (last generally means on top of) + if (dist <= smallestDistance) { + smallestDistance = dist; + item = [i, j / ps]; + } + } + } + + if (s.bars.show && !item) { // no other point can be nearby + var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2, + barRight = barLeft + s.bars.barWidth; + + for (j = 0; j < points.length; j += ps) { + var x = points[j], y = points[j + 1], b = points[j + 2]; + if (x == null) + continue; + + // for a bar graph, the cursor must be inside the bar + if (series[i].bars.horizontal ? + (mx <= Math.max(b, x) && mx >= Math.min(b, x) && + my >= y + barLeft && my <= y + barRight) : + (mx >= x + barLeft && mx <= x + barRight && + my >= Math.min(b, y) && my <= Math.max(b, y))) + item = [i, j / ps]; + } + } + } + + if (item) { + i = item[0]; + j = item[1]; + ps = series[i].datapoints.pointsize; + + return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), + dataIndex: j, + series: series[i], + seriesIndex: i }; + } + + return null; + } + + function onMouseMove(e) { + if (options.grid.hoverable) + triggerClickHoverEvent("plothover", e, + function (s) { return s["hoverable"] != false; }); + } + + function onClick(e) { + triggerClickHoverEvent("plotclick", e, + function (s) { return s["clickable"] != false; }); + } + + // trigger click or hover event (they send the same parameters + // so we share their code) + function triggerClickHoverEvent(eventname, event, seriesFilter) { + var offset = eventHolder.offset(), + pos = { pageX: event.pageX, pageY: event.pageY }, + canvasX = event.pageX - offset.left - plotOffset.left, + canvasY = event.pageY - offset.top - plotOffset.top; + + if (axes.xaxis.used) + pos.x = axes.xaxis.c2p(canvasX); + if (axes.yaxis.used) + pos.y = axes.yaxis.c2p(canvasY); + if (axes.x2axis.used) + pos.x2 = axes.x2axis.c2p(canvasX); + if (axes.y2axis.used) + pos.y2 = axes.y2axis.c2p(canvasY); + + var item = findNearbyItem(canvasX, canvasY, seriesFilter); + + if (item) { + // fill in mouse pos for any listeners out there + item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left); + item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top); + } + + if (options.grid.autoHighlight) { + // clear auto-highlights + for (var i = 0; i < highlights.length; ++i) { + var h = highlights[i]; + if (h.auto == eventname && + !(item && h.series == item.series && h.point == item.datapoint)) + unhighlight(h.series, h.point); + } + + if (item) + highlight(item.series, item.datapoint, eventname); + } + + placeholder.trigger(eventname, [ pos, item ]); + } + + function triggerRedrawOverlay() { + if (!redrawTimeout) + redrawTimeout = setTimeout(drawOverlay, 30); + } + + function drawOverlay() { + redrawTimeout = null; + + // draw highlights + octx.save(); + octx.clearRect(0, 0, canvasWidth, canvasHeight); + octx.translate(plotOffset.left, plotOffset.top); + + var i, hi; + for (i = 0; i < highlights.length; ++i) { + hi = highlights[i]; + + if (hi.series.bars.show) + drawBarHighlight(hi.series, hi.point); + else + drawPointHighlight(hi.series, hi.point); + } + octx.restore(); + + executeHooks(hooks.drawOverlay, [octx]); + } + + function highlight(s, point, auto) { + if (typeof s == "number") + s = series[s]; + + if (typeof point == "number") + point = s.data[point]; + + var i = indexOfHighlight(s, point); + if (i == -1) { + highlights.push({ series: s, point: point, auto: auto }); + + triggerRedrawOverlay(); + } + else if (!auto) + highlights[i].auto = false; + } + + function unhighlight(s, point) { + if (s == null && point == null) { + highlights = []; + triggerRedrawOverlay(); + } + + if (typeof s == "number") + s = series[s]; + + if (typeof point == "number") + point = s.data[point]; + + var i = indexOfHighlight(s, point); + if (i != -1) { + highlights.splice(i, 1); + + triggerRedrawOverlay(); + } + } + + function indexOfHighlight(s, p) { + for (var i = 0; i < highlights.length; ++i) { + var h = highlights[i]; + if (h.series == s && h.point[0] == p[0] + && h.point[1] == p[1]) + return i; + } + return -1; + } + + function drawPointHighlight(series, point) { + var x = point[0], y = point[1], + axisx = series.xaxis, axisy = series.yaxis; + + if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) + return; + + var pointRadius = series.points.radius + series.points.lineWidth / 2; + octx.lineWidth = pointRadius; + octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString(); + var radius = 1.5 * pointRadius; + octx.beginPath(); + octx.arc(axisx.p2c(x), axisy.p2c(y), radius, 0, 2 * Math.PI, false); + octx.stroke(); + } + + function drawBarHighlight(series, point) { + octx.lineWidth = series.bars.lineWidth; + octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString(); + var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString(); + var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; + drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, + 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal); + } + + function getColorOrGradient(spec, bottom, top, defaultColor) { + if (typeof spec == "string") + return spec; + else { + // assume this is a gradient spec; IE currently only + // supports a simple vertical gradient properly, so that's + // what we support too + var gradient = ctx.createLinearGradient(0, top, 0, bottom); + + for (var i = 0, l = spec.colors.length; i < l; ++i) { + var c = spec.colors[i]; + if (typeof c != "string") { + c = $.color.parse(defaultColor).scale('rgb', c.brightness); + c.a *= c.opacity; + c = c.toString(); + } + gradient.addColorStop(i / (l - 1), c); + } + + return gradient; + } + } + } + + $.plot = function(placeholder, data, options) { + var plot = new Plot($(placeholder), data, options, $.plot.plugins); + /*var t0 = new Date(); + var t1 = new Date(); + var tstr = "time used (msecs): " + (t1.getTime() - t0.getTime()) + if (window.console) + console.log(tstr); + else + alert(tstr);*/ + return plot; + }; + + $.plot.plugins = []; + + // returns a string with the date d formatted according to fmt + $.plot.formatDate = function(d, fmt, monthNames) { + var leftPad = function(n) { + n = "" + n; + return n.length == 1 ? "0" + n : n; + }; + + var r = []; + var escape = false; + var hours = d.getUTCHours(); + var isAM = hours < 12; + if (monthNames == null) + monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + + if (fmt.search(/%p|%P/) != -1) { + if (hours > 12) { + hours = hours - 12; + } else if (hours == 0) { + hours = 12; + } + } + for (var i = 0; i < fmt.length; ++i) { + var c = fmt.charAt(i); + + if (escape) { + switch (c) { + case 'h': c = "" + hours; break; + case 'H': c = leftPad(hours); break; + case 'M': c = leftPad(d.getUTCMinutes()); break; + case 'S': c = leftPad(d.getUTCSeconds()); break; + case 'd': c = "" + d.getUTCDate(); break; + case 'm': c = "" + (d.getUTCMonth() + 1); break; + case 'y': c = "" + d.getUTCFullYear(); break; + case 'b': c = "" + monthNames[d.getUTCMonth()]; break; + case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break; + case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break; + } + r.push(c); + escape = false; + } + else { + if (c == "%") + escape = true; + else + r.push(c); + } + } + return r.join(""); + }; + + // round to nearby lower multiple of base + function floorInBase(n, base) { + return base * Math.floor(n / base); + } + +})(jQuery); diff --git a/var/www/public/starmap/js/jquery.flot.selection.js b/var/www/public/starmap/js/jquery.flot.selection.js new file mode 100644 index 00000000..da81c923 --- /dev/null +++ b/var/www/public/starmap/js/jquery.flot.selection.js @@ -0,0 +1,299 @@ +/* +Flot plugin for selecting regions. + +The plugin defines the following options: + + selection: { + mode: null or "x" or "y" or "xy", + color: color + } + +You enable selection support by setting the mode to one of "x", "y" or +"xy". In "x" mode, the user will only be able to specify the x range, +similarly for "y" mode. For "xy", the selection becomes a rectangle +where both ranges can be specified. "color" is color of the selection. + +When selection support is enabled, a "plotselected" event will be emitted +on the DOM element you passed into the plot function. The event +handler gets one extra parameter with the ranges selected on the axes, +like this: + + placeholder.bind("plotselected", function(event, ranges) { + alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to) + // similar for yaxis, secondary axes are in x2axis + // and y2axis if present + }); + +The "plotselected" event is only fired when the user has finished +making the selection. A "plotselecting" event is fired during the +process with the same parameters as the "plotselected" event, in case +you want to know what's happening while it's happening, + +A "plotunselected" event with no arguments is emitted when the user +clicks the mouse to remove the selection. + +The plugin allso adds the following methods to the plot object: + +- setSelection(ranges, preventEvent) + + Set the selection rectangle. The passed in ranges is on the same + form as returned in the "plotselected" event. If the selection + mode is "x", you should put in either an xaxis (or x2axis) object, + if the mode is "y" you need to put in an yaxis (or y2axis) object + and both xaxis/x2axis and yaxis/y2axis if the selection mode is + "xy", like this: + + setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } }); + + setSelection will trigger the "plotselected" event when called. If + you don't want that to happen, e.g. if you're inside a + "plotselected" handler, pass true as the second parameter. + +- clearSelection(preventEvent) + + Clear the selection rectangle. Pass in true to avoid getting a + "plotunselected" event. + +- getSelection() + + Returns the current selection in the same format as the + "plotselected" event. If there's currently no selection, the + function returns null. + +*/ + +(function ($) { + function init(plot) { + var selection = { + first: { x: -1, y: -1}, second: { x: -1, y: -1}, + show: false, + active: false + }; + + // FIXME: The drag handling implemented here should be + // abstracted out, there's some similar code from a library in + // the navigation plugin, this should be massaged a bit to fit + // the Flot cases here better and reused. Doing this would + // make this plugin much slimmer. + var savedhandlers = {}; + + function onMouseMove(e) { + if (selection.active) { + plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]); + + updateSelection(e); + } + } + + function onMouseDown(e) { + if (e.which != 1) // only accept left-click + return; + + // cancel out any text selections + document.body.focus(); + + // prevent text selection and drag in old-school browsers + if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) { + savedhandlers.onselectstart = document.onselectstart; + document.onselectstart = function () { return false; }; + } + if (document.ondrag !== undefined && savedhandlers.ondrag == null) { + savedhandlers.ondrag = document.ondrag; + document.ondrag = function () { return false; }; + } + + setSelectionPos(selection.first, e); + + selection.active = true; + + $(document).one("mouseup", onMouseUp); + } + + function onMouseUp(e) { + // revert drag stuff for old-school browsers + if (document.onselectstart !== undefined) + document.onselectstart = savedhandlers.onselectstart; + if (document.ondrag !== undefined) + document.ondrag = savedhandlers.ondrag; + + // no more draggy-dee-drag + selection.active = false; + updateSelection(e); + + if (selectionIsSane()) + triggerSelectedEvent(); + else { + // this counts as a clear + plot.getPlaceholder().trigger("plotunselected", [ ]); + plot.getPlaceholder().trigger("plotselecting", [ null ]); + } + + return false; + } + + function getSelection() { + if (!selectionIsSane()) + return null; + + var x1 = Math.min(selection.first.x, selection.second.x), + x2 = Math.max(selection.first.x, selection.second.x), + y1 = Math.max(selection.first.y, selection.second.y), + y2 = Math.min(selection.first.y, selection.second.y); + + var r = {}; + var axes = plot.getAxes(); + if (axes.xaxis.used) + r.xaxis = { from: axes.xaxis.c2p(x1), to: axes.xaxis.c2p(x2) }; + if (axes.x2axis.used) + r.x2axis = { from: axes.x2axis.c2p(x1), to: axes.x2axis.c2p(x2) }; + if (axes.yaxis.used) + r.yaxis = { from: axes.yaxis.c2p(y1), to: axes.yaxis.c2p(y2) }; + if (axes.y2axis.used) + r.y2axis = { from: axes.y2axis.c2p(y1), to: axes.y2axis.c2p(y2) }; + return r; + } + + function triggerSelectedEvent() { + var r = getSelection(); + + plot.getPlaceholder().trigger("plotselected", [ r ]); + + // backwards-compat stuff, to be removed in future + var axes = plot.getAxes(); + if (axes.xaxis.used && axes.yaxis.used) + plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]); + } + + function clamp(min, value, max) { + return value < min? min: (value > max? max: value); + } + + function setSelectionPos(pos, e) { + var o = plot.getOptions(); + var offset = plot.getPlaceholder().offset(); + var plotOffset = plot.getPlotOffset(); + pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width()); + pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height()); + + if (o.selection.mode == "y") + pos.x = pos == selection.first? 0: plot.width(); + + if (o.selection.mode == "x") + pos.y = pos == selection.first? 0: plot.height(); + } + + function updateSelection(pos) { + if (pos.pageX == null) + return; + + setSelectionPos(selection.second, pos); + if (selectionIsSane()) { + selection.show = true; + plot.triggerRedrawOverlay(); + } + else + clearSelection(true); + } + + function clearSelection(preventEvent) { + if (selection.show) { + selection.show = false; + plot.triggerRedrawOverlay(); + if (!preventEvent) + plot.getPlaceholder().trigger("plotunselected", [ ]); + } + } + + function setSelection(ranges, preventEvent) { + var axis, range, axes = plot.getAxes(); + var o = plot.getOptions(); + + if (o.selection.mode == "y") { + selection.first.x = 0; + selection.second.x = plot.width(); + } + else { + axis = ranges["xaxis"]? axes["xaxis"]: (ranges["x2axis"]? axes["x2axis"]: axes["xaxis"]); + range = ranges["xaxis"] || ranges["x2axis"] || { from:ranges["x1"], to:ranges["x2"] } + selection.first.x = axis.p2c(Math.min(range.from, range.to)); + selection.second.x = axis.p2c(Math.max(range.from, range.to)); + } + + if (o.selection.mode == "x") { + selection.first.y = 0; + selection.second.y = plot.height(); + } + else { + axis = ranges["yaxis"]? axes["yaxis"]: (ranges["y2axis"]? axes["y2axis"]: axes["yaxis"]); + range = ranges["yaxis"] || ranges["y2axis"] || { from:ranges["y1"], to:ranges["y2"] } + selection.first.y = axis.p2c(Math.min(range.from, range.to)); + selection.second.y = axis.p2c(Math.max(range.from, range.to)); + } + + selection.show = true; + plot.triggerRedrawOverlay(); + if (!preventEvent) + triggerSelectedEvent(); + } + + function selectionIsSane() { + var minSize = 5; + return Math.abs(selection.second.x - selection.first.x) >= minSize && + Math.abs(selection.second.y - selection.first.y) >= minSize; + } + + plot.clearSelection = clearSelection; + plot.setSelection = setSelection; + plot.getSelection = getSelection; + + plot.hooks.bindEvents.push(function(plot, eventHolder) { + var o = plot.getOptions(); + if (o.selection.mode != null) + eventHolder.mousemove(onMouseMove); + + if (o.selection.mode != null) + eventHolder.mousedown(onMouseDown); + }); + + + plot.hooks.drawOverlay.push(function (plot, ctx) { + // draw selection + if (selection.show && selectionIsSane()) { + var plotOffset = plot.getPlotOffset(); + var o = plot.getOptions(); + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + var c = $.color.parse(o.selection.color); + + ctx.strokeStyle = c.scale('a', 0.8).toString(); + ctx.lineWidth = 1; + ctx.lineJoin = "round"; + ctx.fillStyle = c.scale('a', 0.4).toString(); + + var x = Math.min(selection.first.x, selection.second.x), + y = Math.min(selection.first.y, selection.second.y), + w = Math.abs(selection.second.x - selection.first.x), + h = Math.abs(selection.second.y - selection.first.y); + + ctx.fillRect(x, y, w, h); + ctx.strokeRect(x, y, w, h); + + ctx.restore(); + } + }); + } + + $.plot.plugins.push({ + init: init, + options: { + selection: { + mode: null, // one of null, "x", "y" or "xy" + color: "#e8cfac" + } + }, + name: 'selection', + version: '1.0' + }); +})(jQuery); diff --git a/var/www/public/starmap/js/jquery.js b/var/www/public/starmap/js/jquery.js new file mode 100644 index 00000000..92635743 --- /dev/null +++ b/var/www/public/starmap/js/jquery.js @@ -0,0 +1,4376 @@ +/*! + * jQuery JavaScript Library v1.3.2 + * http://jquery.com/ + * + * Copyright (c) 2009 John Resig + * Dual licensed under the MIT and GPL licenses. + * http://docs.jquery.com/License + * + * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) + * Revision: 6246 + */ +(function(){ + +var + // Will speed up references to window, and allows munging its name. + window = this, + // Will speed up references to undefined, and allows munging its name. + undefined, + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + // Map over the $ in case of overwrite + _$ = window.$, + + jQuery = window.jQuery = window.$ = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context ); + }, + + // A simple way to check for HTML strings or ID strings + // (both of which we optimize for) + quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/, + // Is it a simple selector + isSimple = /^.[^:#\[\.,]*$/; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + // Make sure that a selection was provided + selector = selector || document; + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this[0] = selector; + this.length = 1; + this.context = selector; + return this; + } + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + var match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) + selector = jQuery.clean( [ match[1] ], context ); + + // HANDLE: $("#id") + else { + var elem = document.getElementById( match[3] ); + + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem && elem.id != match[3] ) + return jQuery().find( selector ); + + // Otherwise, we inject the element directly into the jQuery object + var ret = jQuery( elem || [] ); + ret.context = document; + ret.selector = selector; + return ret; + } + + // HANDLE: $(expr, [context]) + // (which is just equivalent to: $(content).find(expr) + } else + return jQuery( context ).find( selector ); + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) + return jQuery( document ).ready( selector ); + + // Make sure that old selector state is passed along + if ( selector.selector && selector.context ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return this.setArray(jQuery.isArray( selector ) ? + selector : + jQuery.makeArray(selector)); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.3.2", + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num === undefined ? + + // Return a 'clean' array + Array.prototype.slice.call( this ) : + + // Return just the object + this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = jQuery( elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) + ret.selector = this.selector + (this.selector ? " " : "") + selector; + else if ( name ) + ret.selector = this.selector + "." + name + "(" + selector + ")"; + + // Return the newly-formed element set + return ret; + }, + + // Force the current matched set of elements to become + // the specified array of elements (destroying the stack in the process) + // You should use pushStack() in order to do this, but maintain the stack + setArray: function( elems ) { + // Resetting the length to 0, then using the native Array push + // is a super-fast way to populate an object with array-like properties + this.length = 0; + Array.prototype.push.apply( this, elems ); + + return this; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem && elem.jquery ? elem[0] : elem + , this ); + }, + + attr: function( name, value, type ) { + var options = name; + + // Look for the case where we're accessing a style value + if ( typeof name === "string" ) + if ( value === undefined ) + return this[0] && jQuery[ type || "attr" ]( this[0], name ); + + else { + options = {}; + options[ name ] = value; + } + + // Check to see if we're setting style values + return this.each(function(i){ + // Set all the styles + for ( name in options ) + jQuery.attr( + type ? + this.style : + this, + name, jQuery.prop( this, options[ name ], type, i, name ) + ); + }); + }, + + css: function( key, value ) { + // ignore negative width and height values + if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) + value = undefined; + return this.attr( key, value, "curCSS" ); + }, + + text: function( text ) { + if ( typeof text !== "object" && text != null ) + return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); + + var ret = ""; + + jQuery.each( text || this, function(){ + jQuery.each( this.childNodes, function(){ + if ( this.nodeType != 8 ) + ret += this.nodeType != 1 ? + this.nodeValue : + jQuery.fn.text( [ this ] ); + }); + }); + + return ret; + }, + + wrapAll: function( html ) { + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).clone(); + + if ( this[0].parentNode ) + wrap.insertBefore( this[0] ); + + wrap.map(function(){ + var elem = this; + + while ( elem.firstChild ) + elem = elem.firstChild; + + return elem; + }).append(this); + } + + return this; + }, + + wrapInner: function( html ) { + return this.each(function(){ + jQuery( this ).contents().wrapAll( html ); + }); + }, + + wrap: function( html ) { + return this.each(function(){ + jQuery( this ).wrapAll( html ); + }); + }, + + append: function() { + return this.domManip(arguments, true, function(elem){ + if (this.nodeType == 1) + this.appendChild( elem ); + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function(elem){ + if (this.nodeType == 1) + this.insertBefore( elem, this.firstChild ); + }); + }, + + before: function() { + return this.domManip(arguments, false, function(elem){ + this.parentNode.insertBefore( elem, this ); + }); + }, + + after: function() { + return this.domManip(arguments, false, function(elem){ + this.parentNode.insertBefore( elem, this.nextSibling ); + }); + }, + + end: function() { + return this.prevObject || jQuery( [] ); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: [].push, + sort: [].sort, + splice: [].splice, + + find: function( selector ) { + if ( this.length === 1 ) { + var ret = this.pushStack( [], "find", selector ); + ret.length = 0; + jQuery.find( selector, this[0], ret ); + return ret; + } else { + return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){ + return jQuery.find( selector, elem ); + })), "find", selector ); + } + }, + + clone: function( events ) { + // Do the clone + var ret = this.map(function(){ + if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { + // IE copies events bound via attachEvent when + // using cloneNode. Calling detachEvent on the + // clone will also remove the events from the orignal + // In order to get around this, we use innerHTML. + // Unfortunately, this means some modifications to + // attributes in IE that are actually only stored + // as properties will not be copied (such as the + // the name attribute on an input). + var html = this.outerHTML; + if ( !html ) { + var div = this.ownerDocument.createElement("div"); + div.appendChild( this.cloneNode(true) ); + html = div.innerHTML; + } + + return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0]; + } else + return this.cloneNode(true); + }); + + // Copy the events from the original to the clone + if ( events === true ) { + var orig = this.find("*").andSelf(), i = 0; + + ret.find("*").andSelf().each(function(){ + if ( this.nodeName !== orig[i].nodeName ) + return; + + var events = jQuery.data( orig[i], "events" ); + + for ( var type in events ) { + for ( var handler in events[ type ] ) { + jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); + } + } + + i++; + }); + } + + // Return the cloned set + return ret; + }, + + filter: function( selector ) { + return this.pushStack( + jQuery.isFunction( selector ) && + jQuery.grep(this, function(elem, i){ + return selector.call( elem, i ); + }) || + + jQuery.multiFilter( selector, jQuery.grep(this, function(elem){ + return elem.nodeType === 1; + }) ), "filter", selector ); + }, + + closest: function( selector ) { + var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null, + closer = 0; + + return this.map(function(){ + var cur = this; + while ( cur && cur.ownerDocument ) { + if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) { + jQuery.data(cur, "closest", closer); + return cur; + } + cur = cur.parentNode; + closer++; + } + }); + }, + + not: function( selector ) { + if ( typeof selector === "string" ) + // test special case where just one selector is passed in + if ( isSimple.test( selector ) ) + return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector ); + else + selector = jQuery.multiFilter( selector, this ); + + var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; + return this.filter(function() { + return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; + }); + }, + + add: function( selector ) { + return this.pushStack( jQuery.unique( jQuery.merge( + this.get(), + typeof selector === "string" ? + jQuery( selector ) : + jQuery.makeArray( selector ) + ))); + }, + + is: function( selector ) { + return !!selector && jQuery.multiFilter( selector, this ).length > 0; + }, + + hasClass: function( selector ) { + return !!selector && this.is( "." + selector ); + }, + + val: function( value ) { + if ( value === undefined ) { + var elem = this[0]; + + if ( elem ) { + if( jQuery.nodeName( elem, 'option' ) ) + return (elem.attributes.value || {}).specified ? elem.value : elem.text; + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type == "select-one"; + + // Nothing was selected + if ( index < 0 ) + return null; + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + if ( option.selected ) { + // Get the specifc value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if ( one ) + return value; + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + } + + // Everything else, we just grab the value + return (elem.value || "").replace(/\r/g, ""); + + } + + return undefined; + } + + if ( typeof value === "number" ) + value += ''; + + return this.each(function(){ + if ( this.nodeType != 1 ) + return; + + if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) ) + this.checked = (jQuery.inArray(this.value, value) >= 0 || + jQuery.inArray(this.name, value) >= 0); + + else if ( jQuery.nodeName( this, "select" ) ) { + var values = jQuery.makeArray(value); + + jQuery( "option", this ).each(function(){ + this.selected = (jQuery.inArray( this.value, values ) >= 0 || + jQuery.inArray( this.text, values ) >= 0); + }); + + if ( !values.length ) + this.selectedIndex = -1; + + } else + this.value = value; + }); + }, + + html: function( value ) { + return value === undefined ? + (this[0] ? + this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") : + null) : + this.empty().append( value ); + }, + + replaceWith: function( value ) { + return this.after( value ).remove(); + }, + + eq: function( i ) { + return this.slice( i, +i + 1 ); + }, + + slice: function() { + return this.pushStack( Array.prototype.slice.apply( this, arguments ), + "slice", Array.prototype.slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function(elem, i){ + return callback.call( elem, i, elem ); + })); + }, + + andSelf: function() { + return this.add( this.prevObject ); + }, + + domManip: function( args, table, callback ) { + if ( this[0] ) { + var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(), + scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ), + first = fragment.firstChild; + + if ( first ) + for ( var i = 0, l = this.length; i < l; i++ ) + callback.call( root(this[i], first), this.length > 1 || i > 0 ? + fragment.cloneNode(true) : fragment ); + + if ( scripts ) + jQuery.each( scripts, evalScript ); + } + + return this; + + function root( elem, cur ) { + return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ? + (elem.getElementsByTagName("tbody")[0] || + elem.appendChild(elem.ownerDocument.createElement("tbody"))) : + elem; + } + } +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +function evalScript( i, elem ) { + if ( elem.src ) + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + + else + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + + if ( elem.parentNode ) + elem.parentNode.removeChild( elem ); +} + +function now(){ + return +new Date; +} + +jQuery.extend = jQuery.fn.extend = function() { + // copy reference to target object + var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) + target = {}; + + // extend jQuery itself if only one argument is passed + if ( length == i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) + // Extend the base object + for ( var name in options ) { + var src = target[ name ], copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) + continue; + + // Recurse if we're merging object values + if ( deep && copy && typeof copy === "object" && !copy.nodeType ) + target[ name ] = jQuery.extend( deep, + // Never move original objects, clone them + src || ( copy.length != null ? [ ] : { } ) + , copy ); + + // Don't bring in undefined values + else if ( copy !== undefined ) + target[ name ] = copy; + + } + + // Return the modified object + return target; +}; + +// exclude the following css properties to add px +var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, + // cache defaultView + defaultView = document.defaultView || {}, + toString = Object.prototype.toString; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) + window.jQuery = _jQuery; + + return jQuery; + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return toString.call(obj) === "[object Function]"; + }, + + isArray: function( obj ) { + return toString.call(obj) === "[object Array]"; + }, + + // check if an element is in a (or is an) XML document + isXMLDoc: function( elem ) { + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument ); + }, + + // Evalulates a script in a global context + globalEval: function( data ) { + if ( data && /\S/.test(data) ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + if ( jQuery.support.scriptEval ) + script.appendChild( document.createTextNode( data ) ); + else + script.text = data; + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709). + head.insertBefore( script, head.firstChild ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, length = object.length; + + if ( args ) { + if ( length === undefined ) { + for ( name in object ) + if ( callback.apply( object[ name ], args ) === false ) + break; + } else + for ( ; i < length; ) + if ( callback.apply( object[ i++ ], args ) === false ) + break; + + // A special, fast, case for the most common use of each + } else { + if ( length === undefined ) { + for ( name in object ) + if ( callback.call( object[ name ], name, object[ name ] ) === false ) + break; + } else + for ( var value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} + } + + return object; + }, + + prop: function( elem, value, type, i, name ) { + // Handle executable functions + if ( jQuery.isFunction( value ) ) + value = value.call( elem, i ); + + // Handle passing in a number to a CSS property + return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ? + value + "px" : + value; + }, + + className: { + // internal only, use addClass("class") + add: function( elem, classNames ) { + jQuery.each((classNames || "").split(/\s+/), function(i, className){ + if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) + elem.className += (elem.className ? " " : "") + className; + }); + }, + + // internal only, use removeClass("class") + remove: function( elem, classNames ) { + if (elem.nodeType == 1) + elem.className = classNames !== undefined ? + jQuery.grep(elem.className.split(/\s+/), function(className){ + return !jQuery.className.has( classNames, className ); + }).join(" ") : + ""; + }, + + // internal only, use hasClass("class") + has: function( elem, className ) { + return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; + } + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback ) { + var old = {}; + // Remember the old values, and insert the new ones + for ( var name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + callback.call( elem ); + + // Revert the old values + for ( var name in options ) + elem.style[ name ] = old[ name ]; + }, + + css: function( elem, name, force, extra ) { + if ( name == "width" || name == "height" ) { + var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; + + function getWH() { + val = name == "width" ? elem.offsetWidth : elem.offsetHeight; + + if ( extra === "border" ) + return; + + jQuery.each( which, function() { + if ( !extra ) + val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; + if ( extra === "margin" ) + val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0; + else + val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; + }); + } + + if ( elem.offsetWidth !== 0 ) + getWH(); + else + jQuery.swap( elem, props, getWH ); + + return Math.max(0, Math.round(val)); + } + + return jQuery.curCSS( elem, name, force ); + }, + + curCSS: function( elem, name, force ) { + var ret, style = elem.style; + + // We need to handle opacity special in IE + if ( name == "opacity" && !jQuery.support.opacity ) { + ret = jQuery.attr( style, "opacity" ); + + return ret == "" ? + "1" : + ret; + } + + // Make sure we're using the right name for getting the float value + if ( name.match( /float/i ) ) + name = styleFloat; + + if ( !force && style && style[ name ] ) + ret = style[ name ]; + + else if ( defaultView.getComputedStyle ) { + + // Only "float" is needed here + if ( name.match( /float/i ) ) + name = "float"; + + name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); + + var computedStyle = defaultView.getComputedStyle( elem, null ); + + if ( computedStyle ) + ret = computedStyle.getPropertyValue( name ); + + // We should always get a number back from opacity + if ( name == "opacity" && ret == "" ) + ret = "1"; + + } else if ( elem.currentStyle ) { + var camelCase = name.replace(/\-(\w)/g, function(all, letter){ + return letter.toUpperCase(); + }); + + ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { + // Remember the original values + var left = style.left, rsLeft = elem.runtimeStyle.left; + + // Put in the new values to get a computed value out + elem.runtimeStyle.left = elem.currentStyle.left; + style.left = ret || 0; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + elem.runtimeStyle.left = rsLeft; + } + } + + return ret; + }, + + clean: function( elems, context, fragment ) { + context = context || document; + + // !context.createElement fails in IE with an error but returns typeof 'object' + if ( typeof context.createElement === "undefined" ) + context = context.ownerDocument || context[0] && context[0].ownerDocument || document; + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) { + var match = /^<(\w+)\s*\/?>$/.exec(elems[0]); + if ( match ) + return [ context.createElement( match[1] ) ]; + } + + var ret = [], scripts = [], div = context.createElement("div"); + + jQuery.each(elems, function(i, elem){ + if ( typeof elem === "number" ) + elem += ''; + + if ( !elem ) + return; + + // Convert html string into DOM nodes + if ( typeof elem === "string" ) { + // Fix "XHTML"-style tags in all browsers + elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ + return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? + all : + front + ">"; + }); + + // Trim whitespace, otherwise indexOf won't work as expected + var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase(); + + var wrap = + // option or optgroup + !tags.indexOf("", "" ] || + + !tags.indexOf("", "" ] || + + tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && + [ 1, "", "
" ] || + + !tags.indexOf("", "" ] || + + // matched above + (!tags.indexOf("", "" ] || + + !tags.indexOf("", "" ] || + + // IE can't serialize and + + + + +

Lacuna Alliance Starmap

+ + +
+
+
+ +
+
+ +

+
+ +

Click and drag on either map to zoom in or out. Click on two points to calculate distance. Reset Map

+ +

Show:

+
+

This map would not be possible if not for the work by Empire Name of the Sea of Goats

+ +
+ + + + + diff --git a/var/www/public/starmap/starmap.json b/var/www/public/starmap/starmap.json new file mode 100644 index 00000000..32813ac9 --- /dev/null +++ b/var/www/public/starmap/starmap.json @@ -0,0 +1 @@ +{"map":{"bounds":{"y":[-500,500],"x":[-500,500]}},"alliances":{"11":{"info":["Robtopia (The WAUSAU PAC) -- Cap : (195,-188) [0|0]","Pabst (The WAUSAU PAC) -- Cap : (-78,-29) [0|0]"],"alliance_id":"11","data":[["195","-188"],["-78","-29"]],"label":"The WAUSAU PAC"},"-9":{"info":["DeLambert (DeLambert) -- Cap : (-466,-498) [-1|-1]","DeLambert (DeLambert) -- Col : (-375,-496) [-1|-1]","DeLambert (DeLambert) -- Col : (224,-496) [0|-1]","DeLambert (DeLambert) -- Col : (-220,-492) [0|-1]","DeLambert (DeLambert) -- Col : (-173,-491) [0|-1]","DeLambert (DeLambert) -- Col : (-146,-490) [0|-1]","DeLambert (DeLambert) -- Col : (173,-489) [0|-1]","DeLambert (DeLambert) -- Col : (274,-493) [1|-1]","DeLambert (DeLambert) -- Col : (-377,-243) [-1|0]","DeLambert (DeLambert) -- Col : (-227,-250) [0|0]","DeLambert (DeLambert) -- Col : (-212,-247) [0|0]","DeLambert (DeLambert) -- Col : (-185,-248) [0|0]","DeLambert (DeLambert) -- Col : (-110,-247) [0|0]","DeLambert (DeLambert) -- Col : (-49,-249) [0|0]","DeLambert (DeLambert) -- Col : (-2,-248) [0|0]","DeLambert (DeLambert) -- Col : (269,-246) [1|0]","DeLambert (DeLambert) -- Col : (476,-250) [1|0]","DeLambert (DeLambert) -- Col : (-406,256) [-1|1]","DeLambert (DeLambert) -- Col : (-241,254) [0|1]","DeLambert (DeLambert) -- Col : (282,252) [1|1]"],"alliance_id":"-9","data":[["-466","-498"],["-375","-496"],["224","-496"],["-220","-492"],["-173","-491"],["-146","-490"],["173","-489"],["274","-493"],["-377","-243"],["-227","-250"],["-212","-247"],["-185","-248"],["-110","-247"],["-49","-249"],["-2","-248"],["269","-246"],["476","-250"],["-406","256"],["-241","254"],["282","252"]],"label":"DeLambert"},"26":{"info":["Reavers (The bond) -- Cap : (-105,-496) [0|-1]"],"alliance_id":"26","data":[["-105","-496"]],"label":"The bond"},"17":{"info":["testymctest (Dongitania) -- Cap : (-420,-497) [-1|-1]"],"alliance_id":"17","data":[["-420","-497"]],"label":"Dongitania"},"-7":{"info":["Diablotin (Diablotin) -- Col : (355,-395) [1|-1]","Diablotin (Diablotin) -- Col : (-266,-282) [-1|-1]","Diablotin (Diablotin) -- Col : (416,-184) [1|0]","Diablotin (Diablotin) -- Col : (-222,20) [0|0]","Diablotin (Diablotin) -- Col : (-445,181) [-1|0]","Diablotin (Diablotin) -- Col : (-192,289) [0|1]","Diablotin (Diablotin) -- Cap : (418,435) [1|1]","Diablotin (Diablotin) -- Col : (-274,473) [-1|1]"],"alliance_id":"-7","data":[["355","-395"],["-266","-282"],["416","-184"],["-222","20"],["-445","181"],["-192","289"],["418","435"],["-274","473"]],"label":"Diablotin"},"0":{"info":["Jade (Unaligned) -- Cap : (345,-496) [1|-1]","halkeye (Unaligned) -- Cap : (-102,77) [0|0]","Ygnow (Unaligned) -- Cap : (132,-498) [0|-1]","FatRat (Unaligned) -- Cap : (-15,-496) [0|-1]","New Hope (Unaligned) -- Cap : (162,-270) [0|-1]","Minea Prime (Unaligned) -- Cap : (234,-300) [0|-1]","Nimrod Shaked's Empire (Unaligned) -- Cap : (346,93) [1|0]","Ninjas (Unaligned) -- Cap : (-49,-497) [0|-1]","yydongfang (Unaligned) -- Cap : (-124,-498) [0|-1]","Naeindong (Unaligned) -- Cap : (-338,-493) [-1|-1]","Zxcson (Unaligned) -- Cap : (173,-21) [0|0]","Maylor (Unaligned) -- Cap : (0,-499) [0|-1]","john3 (Unaligned) -- Cap : (83,-493) [0|-1]","Downer (Unaligned) -- Cap : (89,-28) [0|0]","Armin Pasalic's Empire (Unaligned) -- Cap : (-105,-238) [0|0]","te45234 (Unaligned) -- Cap : (-141,157) [0|0]","Toxicbdabomb (Unaligned) -- Cap : (318,39) [1|0]","Zippy (Unaligned) -- Cap : (49,315) [0|1]","Arcon (Unaligned) -- Cap : (41,-497) [0|-1]","Yonatan77 (Unaligned) -- Cap : (285,-496) [1|-1]","Assassin (Unaligned) -- Cap : (-60,-495) [0|-1]","phlone (Unaligned) -- Cap : (495,-499) [1|-1]","Evil Dark Tyrany (Unaligned) -- Cap : (-46,-494) [0|-1]","Theboydes (Unaligned) -- Cap : (-390,-496) [-1|-1]","Contact (Unaligned) -- Cap : (158,97) [0|0]","Andrew Lee's Empire (Unaligned) -- Cap : (269,401) [1|1]","Lucan (Unaligned) -- Cap : (481,191) [1|0]","lacnic (Unaligned) -- Cap : (350,-231) [1|0]","Azrael (Unaligned) -- Cap : (-169,-494) [0|-1]","Zachary (Unaligned) -- Cap : (135,-495) [0|-1]","Anushka (Unaligned) -- Cap : (-255,284) [-1|1]","Dorian (Unaligned) -- Cap : (-406,-346) [-1|-1]","te 34598723 (Unaligned) -- Cap : (-413,-74) [-1|0]","Stellar Conglomerate (Unaligned) -- Cap : (82,-62) [0|0]","te 4350543 (Unaligned) -- Cap : (-293,78) [-1|0]","Dave (Unaligned) -- Cap : (52,-62) [0|0]","te 359641234 (Unaligned) -- Cap : (31,-387) [0|-1]","Texasaswang (Unaligned) -- Cap : (-259,72) [-1|0]","Alberto (Unaligned) -- Cap : (-34,-497) [0|-1]","Zergara (Unaligned) -- Cap : (218,-364) [0|-1]","Highlander (Unaligned) -- Cap : (330,-498) [1|-1]","TIMENIIG (Unaligned) -- Cap : (158,-333) [0|-1]","Miguel (Unaligned) -- Cap : (-271,-500) [-1|-1]","sashimi (Unaligned) -- Cap : (282,262) [1|1]","hebrab (Unaligned) -- Cap : (359,-497) [1|-1]","Valkuryn (Unaligned) -- Cap : (-31,-500) [0|-1]","blah (Unaligned) -- Cap : (299,351) [1|1]","Pete's Empire of Fish (Unaligned) -- Cap : (335,-214) [1|0]","My Empire (Unaligned) -- Cap : (276,-73) [1|0]","john4 (Unaligned) -- Cap : (-7,-474) [0|-1]","Bärenland (Unaligned) -- Cap : (269,-86) [1|0]","Waldo (Unaligned) -- Cap : (267,-499) [1|-1]","Garetj (Unaligned) -- Cap : (-423,-498) [-1|-1]","Emerge (Unaligned) -- Cap : (-368,-152) [-1|0]","Kyle34 (Unaligned) -- Cap : (165,-496) [0|-1]","jobonia_prime (Unaligned) -- Cap : (1,-179) [0|0]","Rylian Order (Unaligned) -- Cap : (417,-497) [1|-1]","Ysthane (Unaligned) -- Cap : (465,-497) [1|-1]","Dayton-218 (Unaligned) -- Cap : (-60,-497) [0|-1]","Zorphemax (Unaligned) -- Cap : (221,-499) [0|-1]","Giankeelandia (Unaligned) -- Cap : (-478,-77) [-1|0]","Prestoria (Unaligned) -- Cap : (-55,-381) [0|-1]","Mousey (Unaligned) -- Cap : (-150,-499) [0|-1]","PlaytesterZ (Unaligned) -- Cap : (-31,141) [0|0]","Comex (Unaligned) -- Cap : (16,-467) [0|-1]","Sweden (Unaligned) -- Cap : (-46,-490) [0|-1]","Blazing guns (Unaligned) -- Cap : (-285,-497) [-1|-1]","Darkness (Unaligned) -- Cap : (101,-499) [0|-1]","AHTest4 (Unaligned) -- Cap : (-106,-495) [0|-1]","Talos (Unaligned) -- Cap : (405,-499) [1|-1]","atashino2 (Unaligned) -- Cap : (-285,-495) [-1|-1]","The Terran Empire (Unaligned) -- Cap : (128,-361) [0|-1]","thegalaXy (Unaligned) -- Cap : (353,-103) [1|0]","Target (Unaligned) -- Cap : (45,-497) [0|-1]","Pat3309 (Unaligned) -- Cap : (-225,-494) [0|-1]","Derando (Unaligned) -- Cap : (60,-496) [0|-1]","Facebook Sucks (Unaligned) -- Cap : (257,-489) [1|-1]","Insanisarium (Unaligned) -- Cap : (97,-443) [0|-1]","Aeri (Unaligned) -- Cap : (-473,-494) [-1|-1]","VXZ (Unaligned) -- Cap : (-240,-497) [0|-1]","Spy Ninjas (Unaligned) -- Cap : (-487,-492) [-1|-1]","Ugglefett (Unaligned) -- Cap : (-112,-321) [0|-1]","Sith Alliance (Unaligned) -- Cap : (13,-409) [0|-1]","Toja of the Divine (Unaligned) -- Cap : (360,-496) [1|-1]","Waste (Unaligned) -- Cap : (-168,-497) [0|-1]","Angelofdarkness (Unaligned) -- Cap : (297,-498) [1|-1]","Jaymeson (Unaligned) -- Cap : (135,-497) [0|-1]","te 43523546 (Unaligned) -- Cap : (352,-62) [1|0]","Harry potter (Unaligned) -- Cap : (134,-498) [0|-1]","zombocom (Unaligned) -- Cap : (278,476) [1|1]","Ma9nu2 (Unaligned) -- Cap : (419,-497) [1|-1]","Evan (Unaligned) -- Cap : (-439,-494) [-1|-1]","SleviN (Unaligned) -- Cap : (75,-498) [0|-1]","SDL Might (Unaligned) -- Cap : (398,56) [1|0]","Voyager (Unaligned) -- Cap : (427,71) [1|0]","Bad Ninja Clan (Unaligned) -- Cap : (195,-496) [0|-1]","vaelxon (Unaligned) -- Cap : (-91,-495) [0|-1]","Thadm (Unaligned) -- Cap : (164,-497) [0|-1]","Observer (Unaligned) -- Cap : (206,-497) [0|-1]","te4567645 (Unaligned) -- Cap : (105,302) [0|1]","Romanus (Unaligned) -- Cap : (-138,-497) [0|-1]","United Federation (Unaligned) -- Cap : (360,-494) [1|-1]","Discordia (Unaligned) -- Cap : (8,466) [0|1]","Moose Hunters (Unaligned) -- Cap : (-340,-493) [-1|-1]","Testr (Unaligned) -- Cap : (337,369) [1|1]","Arrianah (Unaligned) -- Cap : (-120,-498) [0|-1]","te 34958345 (Unaligned) -- Cap : (-226,183) [0|0]","john2 (Unaligned) -- Cap : (58,-317) [0|-1]","Steven (Unaligned) -- Cap : (-450,-498) [-1|-1]","God of boobs (Unaligned) -- Cap : (-244,-497) [0|-1]","te sdfsdfkj (Unaligned) -- Cap : (187,-2) [0|0]","jtest1 (Unaligned) -- Cap : (382,-433) [1|-1]","jtest (Unaligned) -- Cap : (8,88) [0|0]","Eggeria (Unaligned) -- Cap : (-46,-56) [0|0]","Melvinpang (Unaligned) -- Cap : (105,241) [0|0]","Jon1812 (Unaligned) -- Cap : (-33,-500) [0|-1]","RyanKnope (Unaligned) -- Cap : (224,1) [0|0]","An unexploded cow (Unaligned) -- Cap : (-408,-500) [-1|-1]","Ipo (Unaligned) -- Cap : (-301,-498) [-1|-1]","Toqeer (Unaligned) -- Cap : (-69,-174) [0|0]","Crunch (Unaligned) -- Cap : (-316,-496) [-1|-1]","Helios (Unaligned) -- Cap : (104,-500) [0|-1]","aaaaaa (Unaligned) -- Cap : (406,93) [1|0]","icydee (Unaligned) -- Cap : (-212,104) [0|0]","The Z£Ks (Unaligned) -- Cap : (-278,-494) [-1|-1]","Dudesons (Unaligned) -- Cap : (-105,-498) [0|-1]","Puppet (Unaligned) -- Cap : (-150,-497) [0|-1]","God of blood (Unaligned) -- Cap : (-259,-496) [-1|-1]","Apollo (Unaligned) -- Cap : (-128,-493) [0|-1]","Brainpire (Unaligned) -- Cap : (210,-497) [0|-1]","sidian (Unaligned) -- Cap : (-347,392) [-1|1]","Nath1704 (Unaligned) -- Cap : (150,-498) [0|-1]","Alvinvinvin (Unaligned) -- Cap : (89,-500) [0|-1]","Ramon (Unaligned) -- Cap : (-15,-498) [0|-1]","Haarg (Unaligned) -- Cap : (444,126) [1|0]","zendriks lot (Unaligned) -- Cap : (194,-396) [0|-1]","BadChoob (Unaligned) -- Cap : (240,-496) [0|-1]","The Techs (Unaligned) -- Cap : (-141,-303) [0|-1]","Caprica (Unaligned) -- Cap : (357,-497) [1|-1]","R2d2 (Unaligned) -- Cap : (191,-496) [0|-1]","Walish (Unaligned) -- Cap : (-361,-499) [-1|-1]","Shaggedelica (Unaligned) -- Cap : (241,427) [0|1]","Breaker (Unaligned) -- Cap : (-75,-499) [0|-1]","tmtowtdi (Unaligned) -- Cap : (162,-497) [0|-1]","Calis Testings (Unaligned) -- Cap : (416,291) [1|1]","icy (Unaligned) -- Cap : (-352,-494) [-1|-1]","icd (Unaligned) -- Cap : (90,-497) [0|-1]","kiamo (Unaligned) -- Cap : (375,-497) [1|-1]","Kila (Unaligned) -- Cap : (-367,-491) [-1|-1]","McCombs (Unaligned) -- Cap : (57,62) [0|0]","Facebook (Unaligned) -- Cap : (376,234) [1|0]","Scruffy (Unaligned) -- Cap : (-222,-429) [0|-1]","fireartist (Unaligned) -- Cap : (70,4) [0|0]","Kyle (Unaligned) -- Cap : (26,-497) [0|-1]","Wabonk Leha (Unaligned) -- Cap : (59,-499) [0|-1]","Zweebles (Unaligned) -- Cap : (13,423) [0|1]","Louzazz (Unaligned) -- Cap : (12,-497) [0|-1]","Graham Knop's Empire (Unaligned) -- Cap : (227,-207) [0|0]","Bultron Empire (Unaligned) -- Cap : (9,-261) [0|-1]","Lizmalia (Unaligned) -- Cap : (-247,-305) [0|-1]","Zach (Unaligned) -- Cap : (-495,-495) [-1|-1]","Sith (Unaligned) -- Cap : (204,466) [0|1]","Qweson (Unaligned) -- Cap : (7,-42) [0|0]","Simbad (Unaligned) -- Cap : (-405,-499) [-1|-1]","Richheart (Unaligned) -- Cap : (315,-497) [1|-1]","West (Unaligned) -- Cap : (17,-138) [0|0]","Azzadoni (Unaligned) -- Cap : (-218,-492) [0|-1]","Little People (Unaligned) -- Cap : (-280,-494) [-1|-1]","Primordia (Unaligned) -- Cap : (105,-499) [0|-1]","Vishnu Priya's Empire (Unaligned) -- Cap : (181,191) [0|0]","Amitabh (Unaligned) -- Cap : (-421,-494) [-1|-1]","kr117 (Unaligned) -- Cap : (127,-2) [0|0]","Schezar (Unaligned) -- Cap : (323,-359) [1|-1]","The Void (Unaligned) -- Cap : (-76,-496) [0|-1]","FTR (Unaligned) -- Cap : (14,-328) [0|-1]","Z£K (Unaligned) -- Cap : (-235,-494) [0|-1]","holic (Unaligned) -- Cap : (284,-497) [1|-1]","Groodle (Unaligned) -- Cap : (181,264) [0|1]","Bregna (Unaligned) -- Cap : (-258,-499) [-1|-1]"],"alliance_id":0,"data":[["345","-496"],["-102","77"],["132","-498"],["-15","-496"],["162","-270"],["234","-300"],["346","93"],["-49","-497"],["-124","-498"],["-338","-493"],["173","-21"],["0","-499"],["83","-493"],["89","-28"],["-105","-238"],["-141","157"],["318","39"],["49","315"],["41","-497"],["285","-496"],["-60","-495"],["495","-499"],["-46","-494"],["-390","-496"],["158","97"],["269","401"],["481","191"],["350","-231"],["-169","-494"],["135","-495"],["-255","284"],["-406","-346"],["-413","-74"],["82","-62"],["-293","78"],["52","-62"],["31","-387"],["-259","72"],["-34","-497"],["218","-364"],["330","-498"],["158","-333"],["-271","-500"],["282","262"],["359","-497"],["-31","-500"],["299","351"],["335","-214"],["276","-73"],["-7","-474"],["269","-86"],["267","-499"],["-423","-498"],["-368","-152"],["165","-496"],["1","-179"],["417","-497"],["465","-497"],["-60","-497"],["221","-499"],["-478","-77"],["-55","-381"],["-150","-499"],["-31","141"],["16","-467"],["-46","-490"],["-285","-497"],["101","-499"],["-106","-495"],["405","-499"],["-285","-495"],["128","-361"],["353","-103"],["45","-497"],["-225","-494"],["60","-496"],["257","-489"],["97","-443"],["-473","-494"],["-240","-497"],["-487","-492"],["-112","-321"],["13","-409"],["360","-496"],["-168","-497"],["297","-498"],["135","-497"],["352","-62"],["134","-498"],["278","476"],["419","-497"],["-439","-494"],["75","-498"],["398","56"],["427","71"],["195","-496"],["-91","-495"],["164","-497"],["206","-497"],["105","302"],["-138","-497"],["360","-494"],["8","466"],["-340","-493"],["337","369"],["-120","-498"],["-226","183"],["58","-317"],["-450","-498"],["-244","-497"],["187","-2"],["382","-433"],["8","88"],["-46","-56"],["105","241"],["-33","-500"],["224","1"],["-408","-500"],["-301","-498"],["-69","-174"],["-316","-496"],["104","-500"],["406","93"],["-212","104"],["-278","-494"],["-105","-498"],["-150","-497"],["-259","-496"],["-128","-493"],["210","-497"],["-347","392"],["150","-498"],["89","-500"],["-15","-498"],["444","126"],["194","-396"],["240","-496"],["-141","-303"],["357","-497"],["191","-496"],["-361","-499"],["241","427"],["-75","-499"],["162","-497"],["416","291"],["-352","-494"],["90","-497"],["375","-497"],["-367","-491"],["57","62"],["376","234"],["-222","-429"],["70","4"],["26","-497"],["59","-499"],["13","423"],["12","-497"],["227","-207"],["9","-261"],["-247","-305"],["-495","-495"],["204","466"],["7","-42"],["-405","-499"],["315","-497"],["17","-138"],["-218","-492"],["-280","-494"],["105","-499"],["181","191"],["-421","-494"],["127","-2"],["323","-359"],["-76","-496"],["14","-328"],["-235","-494"],["284","-497"],["181","264"],["-258","-499"]],"label":"Unaligned"},"16":{"info":["Tarksum Enterprises (The Big Dealio) -- Cap : (-37,-23) [0|0]","bob3 (The Big Dealio) -- Cap : (201,-214) [0|0]","The Borg Collective (The Big Dealio) -- Cap : (44,-8) [0|0]","The Borg Collective (The Big Dealio) -- SS : (50,-20) [0|0]","bob2 (The Big Dealio) -- Cap : (81,-3) [0|0]","john (The Big Dealio) -- Cap : (28,3) [0|0]","bob1 (The Big Dealio) -- Cap : (6,-5) [0|0]","rathul (The Big Dealio) -- Cap : (-23,-183) [0|0]","Instrumentality (The Big Dealio) -- Cap : (257,416) [1|1]","Vrby (The Big Dealio) -- Cap : (53,-21) [0|0]"],"alliance_id":"16","data":[["-37","-23"],["201","-214"],["44","-8"],["50","-20"],["81","-3"],["28","3"],["6","-5"],["-23","-183"],["257","416"],["53","-21"]],"label":"The Big Dealio"},"-3":{"info":["Trelvestian Sveitarfélagi (Trelvestian Sveitarfélagi) -- Col : (-424,-497) [-1|-1]","Trelvestian Sveitarfélagi (Trelvestian Sveitarfélagi) -- Col : (-409,-499) [-1|-1]","Trelvestian Sveitarfélagi (Trelvestian Sveitarfélagi) -- Col : (-198,-499) [0|-1]","Trelvestian Sveitarfélagi (Trelvestian Sveitarfélagi) -- Col : (252,-500) [1|-1]","Trelvestian Sveitarfélagi (Trelvestian Sveitarfélagi) -- Col : (-425,-248) [-1|0]","Trelvestian Sveitarfélagi (Trelvestian Sveitarfélagi) -- Col : (-199,-247) [0|0]","Trelvestian Sveitarfélagi (Trelvestian Sveitarfélagi) -- Col : (251,-250) [1|0]","Trelvestian Sveitarfélagi (Trelvestian Sveitarfélagi) -- Col : (-408,154) [-1|0]","Trelvestian Sveitarfélagi (Trelvestian Sveitarfélagi) -- Col : (223,151) [0|0]","Trelvestian Sveitarfélagi (Trelvestian Sveitarfélagi) -- Col : (-326,248) [-1|1]","Trelvestian Sveitarfélagi (Trelvestian Sveitarfélagi) -- Cap : (318,249) [1|1]","Trelvestian Sveitarfélagi (Trelvestian Sveitarfélagi) -- Col : (-199,254) [0|1]"],"alliance_id":"-3","data":[["-424","-497"],["-409","-499"],["-198","-499"],["252","-500"],["-425","-248"],["-199","-247"],["251","-250"],["-408","154"],["223","151"],["-326","248"],["318","249"],["-199","254"]],"label":"Trelvestian Sveitarfélagi"},"25":{"info":["TheKi (Jazz) -- Cap : (270,251) [1|1]"],"alliance_id":"25","data":[["270","251"]],"label":"Jazz"},"28":{"info":["Jeton (France Télécom) -- Cap : (-81,-480) [0|-1]"],"alliance_id":"28","data":[["-81","-480"]],"label":"France Télécom"},"12":{"info":["The Tsurani Empire (The Orion Confederation) -- Cap : (2,-356) [0|-1]"],"alliance_id":"12","data":[["2","-356"]],"label":"The Orion Confederation"},"15":{"info":["Deeno (East Lacuna Trading Company) -- Cap : (424,70) [1|0]"],"alliance_id":"15","data":[["424","70"]],"label":"East Lacuna Trading Company"},"20":{"info":["The Empire of Butt (The Alliance of Butt) -- Cap : (-195,-498) [0|-1]"],"alliance_id":"20","data":[["-195","-498"]],"label":"The Alliance of Butt"},"8":{"info":["2bob (BOB UNITED) -- Cap : (13,-76) [0|0]"],"alliance_id":"8","data":[["13","-76"]],"label":"BOB UNITED"},"4":{"info":["Khenn (Lacunamatata) -- Cap : (11,2) [0|0]","Gratch (Lacunamatata) -- Cap : (44,-30) [0|0]"],"alliance_id":"4","data":[["11","2"],["44","-30"]],"label":"Lacunamatata"},"31":{"info":["The Human Empire (Human Empire Union) -- Cap : (225,-497) [0|-1]"],"alliance_id":"31","data":[["225","-497"]],"label":"Human Empire Union"},"-1":{"info":["Sābēn Demesne (Sābēn Demesne) -- Col : (-394,-494) [-1|-1]","Sābēn Demesne (Sābēn Demesne) -- Cap : (416,256) [1|1]"],"alliance_id":"-1","data":[["-394","-494"],["416","256"]],"label":"Sābēn Demesne"}}} \ No newline at end of file -- 2.51.2 From 4ffdc1a8e6fbb0dc5bdb88fc67f50e6f5b38cd08 Mon Sep 17 00:00:00 2001 From: lemming552 Date: Mon, 2 Jan 2012 17:08:29 -0700 Subject: [PATCH 24/75] Starmap change. --- var/www/public/starmap/index.html | 2 +- var/www/public/starmap/sectors.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/var/www/public/starmap/index.html b/var/www/public/starmap/index.html index 8fc40ec7..19aa9f20 100644 --- a/var/www/public/starmap/index.html +++ b/var/www/public/starmap/index.html @@ -38,7 +38,7 @@ $(function () { function getData() { // TODO get starmap.json from amazonaws - $.getJSON("starmap.json", + $.getJSON("/starmap.json", function(starmap) { var label_map = {}; diff --git a/var/www/public/starmap/sectors.html b/var/www/public/starmap/sectors.html index e8e6aaed..c53e8d94 100644 --- a/var/www/public/starmap/sectors.html +++ b/var/www/public/starmap/sectors.html @@ -38,7 +38,7 @@ $(function () { // TODO get starmap.json from amazonaws function getData() { - $.getJSON("starmap.json", + $.getJSON("/starmap.json", function(starmap) { var label_map = {}; -- 2.51.2 From 559756642563880c9628ce4289dfacc9f067d50c Mon Sep 17 00:00:00 2001 From: lemming552 Date: Mon, 2 Jan 2012 23:40:39 -0700 Subject: [PATCH 25/75] docks available less than or equal to ship count. --- lib/Lacuna/Role/Ship/Trade.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Lacuna/Role/Ship/Trade.pm b/lib/Lacuna/Role/Ship/Trade.pm index da991642..bb77b512 100644 --- a/lib/Lacuna/Role/Ship/Trade.pm +++ b/lib/Lacuna/Role/Ship/Trade.pm @@ -41,7 +41,7 @@ sub _can_target_accept_ships { if (not defined $spaceport) { confess $no_spaceport_exception; } - if ($spaceport->docks_available < $ship_count) { + if ($spaceport->docks_available <= $ship_count) { confess $no_docks_exception; } } -- 2.51.2 From 1c72eebfd8e6f9ec30b491ea16bf225dda2ada1a Mon Sep 17 00:00:00 2001 From: lemming552 Date: Tue, 3 Jan 2012 06:43:26 -0700 Subject: [PATCH 26/75] Too much commenting out. --- bin/summarize_server.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/summarize_server.pl b/bin/summarize_server.pl index 50cbbda4..ad0a1e73 100644 --- a/bin/summarize_server.pl +++ b/bin/summarize_server.pl @@ -572,7 +572,7 @@ sub output_map { $output{alliances}->{$key}->{data} = \@data; } } -# my $json_txt = JSON->new->utf8->encode(\%output); + my $json_txt = JSON->new->utf8->encode(\%output); # open(OUT, ">:utf8:", "starmap.json"); # print OUT $json_txt; # close(OUT); -- 2.51.2 From 5d75aec8fba1a2c60fec63b614cb2d39c3f50d1c Mon Sep 17 00:00:00 2001 From: ian docherty Date: Tue, 3 Jan 2012 14:30:13 -0500 Subject: [PATCH 27/75] Better layout for the Delamberti war table --- lib/Lacuna/Web/Admin.pm | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/Lacuna/Web/Admin.pm b/lib/Lacuna/Web/Admin.pm index faa9a9e3..45cfe9c1 100644 --- a/lib/Lacuna/Web/Admin.pm +++ b/lib/Lacuna/Web/Admin.pm @@ -1260,12 +1260,12 @@ sub www_delambert_war { } @ai_defence; # If the AI attacks, we just care about when the AI wins the attack - my %attack = map { $_->defending_empire_id => $_->attack_victories + $_->attack_spy_hours * 2 } @ai_defence; + my %attack = map { $_->defending_empire_id => $_->attack_victories + $_->attack_spy_hours * 2 } @ai_attack; # Sort the attackers so that those who have done the most un-retaliated damage are shown first - my @worst_attackers = sort {( $defence{$b}{weight} - defined $attack{$b} ? $attack{$b} : 0) <=> ( $defence{$a}{weight} - defined $attack{$a} ? $attack{$a} : 0 ) } keys %defence; + my @worst_attackers = sort {( $defence{$a}{weight} - defined $attack{$a} ? $attack{$a} : 0) <=> ( $defence{$b}{weight} - defined $attack{$b} ? $attack{$b} : 0 ) } keys %defence; - $out .= "\n"; + $out .= "
AttackerVictoriesDefeatsSpy HoursCounter AttacksWeightColonyFrequencyAttack SweepersAttack ScowsAttack SnarkAction
\n"; ATTACKER: foreach my $attacker (@worst_attackers) { my $attack_empire = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->find($attacker); @@ -1315,6 +1315,14 @@ ATTACKER: $out .= ""; } $out .= "
AttackerVictoriesDefeatsSpy HoursRetaliate WeightAttack WeightColonyFrequencyAttack SweepersAttack ScowsAttack SnarkAction
\n"; + $out .= "
    \n"; + $out .= "
  • Victories, Defeats and Spy hours are attacks against the DeLamberti
  • "; + $out .= "
  • Retaliate Weight, is the factor which measures the AI Retaliation against those attacks
  • "; + $out .= "
  • Attack Weight, is a measure of the amount of attacks against the AI
  • "; + $out .= "
  • The list is sorted so that those empires with the highest (Attack Weight - Retaliate Weight) are first
  • "; + $out .= "
\n"; + + return $self->wrap($out); } -- 2.51.2 From 870dbdc56fcaa63de77801f565256c7c4c87e0be Mon Sep 17 00:00:00 2001 From: ian docherty Date: Tue, 3 Jan 2012 16:21:44 -0500 Subject: [PATCH 28/75] Fixed display of incoming allied ships --- lib/Lacuna/DB/Result/Map/Body/Planet.pm | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Lacuna/DB/Result/Map/Body/Planet.pm b/lib/Lacuna/DB/Result/Map/Body/Planet.pm index c7bc28c1..d51f2581 100644 --- a/lib/Lacuna/DB/Result/Map/Body/Planet.pm +++ b/lib/Lacuna/DB/Result/Map/Body/Planet.pm @@ -204,15 +204,15 @@ around get_status => sub { my $num_incoming_ally = 0; my @incoming_ally; # If we are in an alliance, all ships coming from ally (which are not ourself) - if ($self->alliance_id) { + if ($self->empire->alliance_id) { my $incoming_ally_rs = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->search({ foreign_body_id => $self->id, direction => 'out', task => 'Travelling', 'body.empire_id' => {'!=' => $self->empire_id}, - 'body.alliance_id' => $self->alliance_id, + 'empire.alliance_id' => $self->empire->alliance_id, },{ - join => 'body', + join => {body => 'empire'}, order_by => 'date_available', }); $num_incoming_ally = $incoming_ally_rs->count; @@ -238,12 +238,12 @@ around get_status => sub { task => 'Travelling', 'body.empire_id' => {'!=' => $self->empire_id}, },{ - join => 'body', + join => {body => 'empire'}, order_by => 'date_available', }); - if ($self->alliance_id) { + if ($self->empire->alliance_id) { $incoming_enemy_rs = $incoming_enemy_rs->search({ - 'body.alliance_id' => {'!=' => $self->alliance_id}, + 'empire.alliance_id' => {'!=' => $self->empire->alliance_id}, }); } my $num_incoming_enemy = $incoming_enemy_rs->count; -- 2.51.2 From 8b39f900447927649debb82b781948449a67f8ef Mon Sep 17 00:00:00 2001 From: ian docherty Date: Tue, 3 Jan 2012 16:27:57 -0500 Subject: [PATCH 29/75] DeLambert should not attack from the Neutral zone --- lib/Lacuna/AI/DeLambert.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Lacuna/AI/DeLambert.pm b/lib/Lacuna/AI/DeLambert.pm index 877931a5..e7454e56 100644 --- a/lib/Lacuna/AI/DeLambert.pm +++ b/lib/Lacuna/AI/DeLambert.pm @@ -449,7 +449,11 @@ TARGET: my @sweepers; my @scows; my @snarks; +DEL_COLONY: foreach my $del_colony (@del_colonies) { + # Don't attack from the Neutral Zone + next DEL_COLONY if $del_colony->x == -3 and $del_colony->y == 0; + if (@sweepers < $num_sweepers) { my @ships = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->search({ type => 'sweeper', -- 2.51.2 From 2ef17857730dbf6b793d5f8b0322eea54d2069e7 Mon Sep 17 00:00:00 2001 From: ian docherty Date: Tue, 3 Jan 2012 17:15:31 -0500 Subject: [PATCH 30/75] Another fix to ensure that the number of enemy incoming is correct --- lib/Lacuna/DB/Result/Map/Body/Planet.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Lacuna/DB/Result/Map/Body/Planet.pm b/lib/Lacuna/DB/Result/Map/Body/Planet.pm index d51f2581..df0c19e5 100644 --- a/lib/Lacuna/DB/Result/Map/Body/Planet.pm +++ b/lib/Lacuna/DB/Result/Map/Body/Planet.pm @@ -243,7 +243,10 @@ around get_status => sub { }); if ($self->empire->alliance_id) { $incoming_enemy_rs = $incoming_enemy_rs->search({ - 'empire.alliance_id' => {'!=' => $self->empire->alliance_id}, + 'empire.alliance_id' => [ + {'!=' => $self->empire->alliance_id}, + undef, + ] }); } my $num_incoming_enemy = $incoming_enemy_rs->count; -- 2.51.2 From a410644de3c1d46113e4f943c5d3958b5af65903 Mon Sep 17 00:00:00 2001 From: ian docherty Date: Tue, 3 Jan 2012 18:46:22 -0500 Subject: [PATCH 31/75] Uncommented out most of the DeLambert functions. oops! --- lib/Lacuna/AI/DeLambert.pm | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/Lacuna/AI/DeLambert.pm b/lib/Lacuna/AI/DeLambert.pm index e7454e56..29a798e0 100644 --- a/lib/Lacuna/AI/DeLambert.pm +++ b/lib/Lacuna/AI/DeLambert.pm @@ -105,17 +105,17 @@ sub ship_building_priorities { sub run_hourly_colony_updates { my ($self, $colony) = @_; -# $self->demolish_bleeders($colony); -# $self->set_defenders($colony); -# $self->pod_check($colony, 20); -# $self->repair_buildings($colony); -# $self->train_spies($colony, 1); -# $self->build_ships_max($colony); -# $self->run_missions($colony); -# $self->buy_trade($colony); -# $self->sell_glyph_trade($colony); -# $self->sell_plan_trade($colony); -# $self->check_enemy_spy_action($colony); + $self->demolish_bleeders($colony); + $self->set_defenders($colony); + $self->pod_check($colony, 20); + $self->repair_buildings($colony); + $self->train_spies($colony, 1); + $self->build_ships_max($colony); + $self->run_missions($colony); + $self->buy_trade($colony); + $self->sell_glyph_trade($colony); + $self->sell_plan_trade($colony); + $self->check_enemy_spy_action($colony); } sub run_hourly_empire_updates { -- 2.51.2 From 3cd0cdbfb7b8eb9c3ff7b73da608a7ac98ead2e5 Mon Sep 17 00:00:00 2001 From: lemming552 Date: Tue, 3 Jan 2012 16:48:34 -0700 Subject: [PATCH 32/75] Removing unneeded commented out test code. --- bin/summarize_server.pl | 3 --- 1 file changed, 3 deletions(-) diff --git a/bin/summarize_server.pl b/bin/summarize_server.pl index ad0a1e73..3e0672fe 100644 --- a/bin/summarize_server.pl +++ b/bin/summarize_server.pl @@ -573,9 +573,6 @@ sub output_map { } } my $json_txt = JSON->new->utf8->encode(\%output); -# open(OUT, ">:utf8:", "starmap.json"); -# print OUT $json_txt; -# close(OUT); out('Write Map To S3'); my $config = Lacuna->config; my $s3 = SOAP::Amazon::S3->new($config->get('access_key'), $config->get('secret_key'), { RaiseError => 1 }); -- 2.51.2 From f5561a3fb75f2ba0a533338b7be7726a70145b7f Mon Sep 17 00:00:00 2001 From: lemming552 Date: Tue, 3 Jan 2012 22:11:00 -0700 Subject: [PATCH 33/75] Default to show Saben. Remove test starmap from directory. --- var/www/public/starmap/index.html | 9 ++++----- var/www/public/starmap/sectors.html | 9 ++++----- var/www/public/starmap/starmap.json | 1 - 3 files changed, 8 insertions(+), 11 deletions(-) delete mode 100644 var/www/public/starmap/starmap.json diff --git a/var/www/public/starmap/index.html b/var/www/public/starmap/index.html index 19aa9f20..606b08a7 100644 --- a/var/www/public/starmap/index.html +++ b/var/www/public/starmap/index.html @@ -37,7 +37,6 @@ $(function () { function getData() { -// TODO get starmap.json from amazonaws $.getJSON("/starmap.json", function(starmap) { @@ -64,8 +63,8 @@ $(function () { for (var idx in keylist) { var key = keylist[idx]; var val = starmap.alliances[key]; - if (key == '-2') { - // by default only check the Diablotin + if (key == '-1') { + // by default only check the Saben choiceContainer.append('
' + '