diff --git a/bin/run_hourly.sh b/bin/run_hourly.sh index 894dd770..9f04a099 100755 --- a/bin/run_hourly.sh +++ b/bin/run_hourly.sh @@ -17,3 +17,4 @@ perl cult/hourly_update.pl >>/tmp/hourly.log 2>>/tmp/hourly.log perl tick_fissures.pl >>/tmp/tick_fissures.log 2>>/tmp/tick_fissures.log perl test_weather.pl >>/tmp/test_weather.csv 2>/tmp/test_weather.log perl trelvestian/send_attack.pl >>/tmp/attack_trel.log 2>>/tmp/attack_trel.log & +perl tick_stations.pl >>/tmp/hourly.log 2>>/tmp/hourly.log diff --git a/bin/setup/init_lacuna.pl b/bin/setup/init_lacuna.pl index 5fad73c3..d95e30eb 100644 --- a/bin/setup/init_lacuna.pl +++ b/bin/setup/init_lacuna.pl @@ -13,6 +13,12 @@ my $lacunans_have_been_placed = 0; my $t = [Time::HiRes::tv_interval]; create_database(); + +# to test create db only, set env var, useful for testing db changes without +# rebuilding full star map, don't forget to change +# /data/Lacuna-Server/etc/lacuna.conf's db->dsn field to a new db first. +exit 0 if $ENV{CREATE_DB_ONLY}; + open my $star_names, "<", "../../var/starnames.txt"; create_star_map(); close $star_names; diff --git a/bin/tick_stations.pl b/bin/tick_stations.pl new file mode 100644 index 00000000..00ecd227 --- /dev/null +++ b/bin/tick_stations.pl @@ -0,0 +1,16 @@ +use 5.10.0; +use strict; +use lib '/data/Lacuna-Server/lib'; +use Getopt::Long; + +use L; + +GetOptions( + 'quiet!' => \$quiet, +); + +out("Started"); + +LD->resultset("Map::Star")->recalc_control; + +out("Done."); diff --git a/bin/util/fix_seizure.pl b/bin/util/fix_seizure.pl deleted file mode 100644 index d73d44ea..00000000 --- a/bin/util/fix_seizure.pl +++ /dev/null @@ -1,108 +0,0 @@ -use 5.010; -use strict; -use lib '/data/Lacuna-Server/lib'; -use Lacuna::DB; -use Lacuna; -use Lacuna::Util qw(randint format_date); -use Getopt::Long; -$|=1; -our $quiet; -GetOptions( - 'quiet' => \$quiet, -); - - -out('Started'); -my $start = time; - -out('Loading DB'); -our $db = Lacuna->db; - -out('Getting Seized Stars'); -my %seized_stars = map { $_->id => 1 } $db->resultset('Map::Star')->search({ - station_id => {'!=' => undef}, - }); - -my $laws = $db->resultset('Lacuna::DB::Result::Laws')->search({ - type => 'Jurisdiction', - }); - -while ( my $law = $laws->next) { - my $station = $db->resultset('Map::Body')->find($law->station_id); - if ($station) { - $seized_stars{$law->star_id} = 1 if ($seized_stars{$law->star_id} == ''); - if ($station->in_range_of_influence($law->star)) { - my $star = $law->star; - if ($seized_stars{$law->star_id} == 1) { - my $name = 'Seize '.$star->name; - $name = substr($name,0,30); - my $desc = 'Seize control of {Starmap '.$star->x.' '.$star->y.' '.$star->name.'} by {Planet '.$station->id.' '. - $station->name.'}, and apply all present laws to said star and its inhabitants.', - $seized_stars{$law->star_id} = 2; - if ($law->name ne $name or $law->description ne $desc) { - out($law->id." ".$name." - ".$desc."."); - $seized_stars{$law->star_id} = 3; - $law->name("$name"); - $law->description("$desc"); - $law->update; - } - } - else { - out("Law #".$law->id." is a duplicate from ".$law->station_id."."); -# $law->delete; - } - } - else { - out("Law #".$law->id." of ".$law->station_id." is for out of range star".$law->star_id."."); - $seized_stars{$law->star_id} = -1; - $law->delete; - } - } - else { - out("Law #".$law->id." is attached to non-station ".$law->station_id."."); - $seized_stars{$law->star_id} = -2; - $law->delete; - } -} - -for my $star_id (sort keys %seized_stars) { - if ($seized_stars{$star_id} == -2) { - out($star_id." was seized by a bogus law."); - } - elsif ($seized_stars{$star_id} == -1) { - out($star_id." was out of range."); - } - elsif ($seized_stars{$star_id} == 1) { - out($star_id." needs a law to be inserted."); - my $star = $db->resultset('Map::Star')->find($star_id); - my $station_id = $star->station_id; - my $station = $db->resultset('Map::Body')->find($station_id); - my $law = Lacuna->db->resultset('Lacuna::DB::Result::Laws')->new({ - name => 'Seize '.$star->name, - description => 'Seize control of {Starmap '.$star->x.' '.$star->y.' '.$star->name.'} by {Planet '.$station->id.' '. - $station->name.'}, and apply all present laws to said star and its inhabitants.', - type => 'Jurisdiction', - station_id => $station->id, - star_id => $star->id, - }); - $law->star($star); - $law->insert; - } - elsif ($seized_stars{$star_id} == 2) { -# out($star_id." was in no need of change."); - } - elsif ($seized_stars{$star_id} == 3) { - out($star_id." needed the name and desc to be updated."); - } -} - -############### -## SUBROUTINES -############### - -sub out { - my $message = shift; - unless ($quiet) { - say format_date(DateTime->now), " ", $message; - } -} diff --git a/bin/util/swap_bodies.pl b/bin/util/swap_bodies.pl index 602fcd93..16044343 100644 --- a/bin/util/swap_bodies.pl +++ b/bin/util/swap_bodies.pl @@ -217,12 +217,7 @@ sub drop_stars_beyond_range { my ($station) = @_; return 0 if ($station->get_type ne 'space station'); - my $laws = $station->laws->search({type => 'Jurisdiction'}); - while (my $law = $laws->next) { - unless ($station->in_range_of_influence($law->star)) { - $law->delete; - } - } + $station->update_influence(); return 1; } diff --git a/docs/Parliament.pod b/docs/Parliament.pod index 95555fa7..b027dee5 100644 --- a/docs/Parliament.pod +++ b/docs/Parliament.pod @@ -345,42 +345,6 @@ The id of an empire within the alliance that wants control over the station. -=head2 propose_seize_star ( session_id, building_id, star_id ) - -Take control of a star. Usable at parliament level 7. - - { - "status" : { ... }, - "proposition" : { - "id" : "id-goes-here", - "name" : "Seize Sol", - "description" : "Seize control of Sol.", - "votes_needed" : 7, - "votes_yes" : 0, - "votes_no" : 0, - "status" : "Pending", - "date_ends" : "01 31 2010 13:09:05 +0600", - "proposed_by" : { - "id" : "id-goes-here", - "name" : "Klingons", - } - } - } - -=head3 session_id - -A session id. - -=head3 building_id - -The unique id of the parliament. - -=head3 star_id - -The id of a star the station should control. - - - =head2 propose_rename_star ( session_id, building_id, star_id, name ) Rename a star. Usable at parliament level 8. diff --git a/lib/L.pm b/lib/L.pm index 95ab1761..ddce0223 100644 --- a/lib/L.pm +++ b/lib/L.pm @@ -1,5 +1,7 @@ package L; +use 5.10.0; + # Intended to be used from the command line to save a bunch of typing. # This derives off Lacuna, thus can be used like Lacuna: @@ -9,6 +11,18 @@ use parent 'Lacuna'; use LD; use LR; -use L; +# for helping with scripts in bin: + +use Exporter qw(import); +our @EXPORT = qw( $quiet out ); + +our $quiet; +sub out { + my $message = shift; + unless ($quiet) { + say DateTime->now, " ", $message; + } +} + 1; diff --git a/lib/Lacuna/Constants.pm b/lib/Lacuna/Constants.pm index 754f4fff..4bef1bbc 100644 --- a/lib/Lacuna/Constants.pm +++ b/lib/Lacuna/Constants.pm @@ -6,6 +6,7 @@ use base 'Exporter'; use constant INFLATION => 1.75; use constant SECONDS_IN_A_DAY => 60 * 60 * 24; use constant GROWTH => 1.55; +use constant MINIMUM_EXERTABLE_INFLUENCE => 10; use constant FOOD_TYPES => (qw(cheese bean lapis potato apple root corn cider wheat bread soup chip pie pancake milk meal algae syrup fungus burger shake beetle)); use constant ORE_TYPES => (qw(rutile chromite chalcopyrite galena gold uraninite bauxite goethite halite gypsum trona kerogen methane anthracite sulfur zircon monazite fluorite beryl magnetite)); use constant BUILDABLE_CLASSES => (qw( @@ -134,6 +135,7 @@ our @EXPORT_OK = qw( INFLATION SECONDS_IN_A_DAY GROWTH + MINIMUM_EXERTABLE_INFLUENCE FOOD_TYPES ORE_TYPES BUILDABLE_CLASSES @@ -149,6 +151,7 @@ our %EXPORT_TAGS = ( INFLATION SECONDS_IN_A_DAY GROWTH + MINIMUM_EXERTABLE_INFLUENCE FOOD_TYPES ORE_TYPES BUILDABLE_CLASSES diff --git a/lib/Lacuna/DB.pm b/lib/Lacuna/DB.pm index f63c8c3e..6b2ded57 100644 --- a/lib/Lacuna/DB.pm +++ b/lib/Lacuna/DB.pm @@ -104,5 +104,17 @@ sub ships { $self->resultset('Ships')->search($where); } +sub X { + my ($self, $type, $id) = @_; + my $where = $self->_where(name => $id); + $self->resultset($type)->find($where); +} + +sub XX { + my ($self, $type, $id) = @_; + my $where = $self->_where(name => $id); + $self->resultset($type)->search($where); +} + no Moose; __PACKAGE__->meta->make_immutable; diff --git a/lib/Lacuna/DB/Result/Building/Module/IBS.pm b/lib/Lacuna/DB/Result/Building/Module/IBS.pm index c9d1bfaa..9e755e04 100644 --- a/lib/Lacuna/DB/Result/Building/Module/IBS.pm +++ b/lib/Lacuna/DB/Result/Building/Module/IBS.pm @@ -12,26 +12,6 @@ use constant name => 'Interstellar Broadcast System'; use constant max_instances_per_planet => 1; use constant energy_consumption => 160; -before demolish => sub { - my $self = shift; - my $laws = $self->body->laws->search({type => 'Jurisdiction'}); - while (my $law = $laws->next) { - $law->delete; - } -}; - -# after downgrade do we have the new range -after downgrade => sub { - my $self = shift; - my $station = $self->body; - my $laws = $station->laws->search({type => 'Jurisdiction'}); - while (my $law = $laws->next) { - unless ($station->in_range_of_influence($law->star)) { - $law->delete; - } - } -}; - no Moose; __PACKAGE__->meta->make_immutable(inline_constructor => 0); diff --git a/lib/Lacuna/DB/Result/Building/Module/Parliament.pm b/lib/Lacuna/DB/Result/Building/Module/Parliament.pm index e250b9b4..531bcdb1 100644 --- a/lib/Lacuna/DB/Result/Building/Module/Parliament.pm +++ b/lib/Lacuna/DB/Result/Building/Module/Parliament.pm @@ -29,7 +29,6 @@ after downgrade => sub { my $level = $self->effective_level; push @unsupported_laws, 'Writ' if $level < 4; - push @unsupported_laws, 'Jurisdiction' if $level < 7; push @unsupported_laws, 'MembersOnlyMiningRights' if $level < 13; push @unsupported_laws, 'Taxation' if $level < 15; push @unsupported_laws, 'MembersOnlyColonization' if $level < 18; diff --git a/lib/Lacuna/DB/Result/Laws.pm b/lib/Lacuna/DB/Result/Laws.pm index 862f1007..f9822f1e 100644 --- a/lib/Lacuna/DB/Result/Laws.pm +++ b/lib/Lacuna/DB/Result/Laws.pm @@ -21,7 +21,6 @@ __PACKAGE__->add_columns( __PACKAGE__->typecast_map(type => { BHGNeutralized => 'Lacuna::DB::Result::Laws::BHGNeutralized', - Jurisdiction => 'Lacuna::DB::Result::Laws::Jurisdiction', MembersOnlyColonization => 'Lacuna::DB::Result::Laws::MembersOnlyColonization', MembersOnlyMiningRights => 'Lacuna::DB::Result::Laws::MembersOnlyMiningRights', MembersOnlyExcavation => 'Lacuna::DB::Result::Laws::MembersOnlyExcavation', diff --git a/lib/Lacuna/DB/Result/Laws/Jurisdiction.pm b/lib/Lacuna/DB/Result/Laws/Jurisdiction.pm deleted file mode 100644 index 140dca2f..00000000 --- a/lib/Lacuna/DB/Result/Laws/Jurisdiction.pm +++ /dev/null @@ -1,23 +0,0 @@ -package Lacuna::DB::Result::Laws::Jurisdiction; - -use Moose; -use utf8; -no warnings qw(uninitialized); -extends 'Lacuna::DB::Result::Laws'; - -before delete => sub { - my $self = shift; - my $star = $self->star; - $star->station_id(undef); - $star->update; -}; - -before insert => sub { - my $self = shift; - my $star = $self->star; - $star->station_id($self->station_id); - $star->update; -}; - -no Moose; -__PACKAGE__->meta->make_immutable(inline_constructor => 0); diff --git a/lib/Lacuna/DB/Result/Map/Body/Planet/Station.pm b/lib/Lacuna/DB/Result/Map/Body/Planet/Station.pm index 00c88ef6..6db94459 100644 --- a/lib/Lacuna/DB/Result/Map/Body/Planet/Station.pm +++ b/lib/Lacuna/DB/Result/Map/Body/Planet/Station.pm @@ -5,6 +5,7 @@ use utf8; no warnings qw(uninitialized); extends 'Lacuna::DB::Result::Map::Body::Planet'; use Lacuna::Util qw(randint); +use Lacuna::Constants qw(MINIMUM_EXERTABLE_INFLUENCE); use Data::Dumper; use constant image => 'station'; @@ -170,20 +171,154 @@ sub _build_range_of_influence { my $range = 0; my ($ibs) = grep {$_->class eq 'Lacuna::DB::Result::Building::Module::IBS'} @{$self->building_cache}; if (defined $ibs) { - $range = $ibs->effective_level * 1000; + $range = $ibs->level * 300; } return $range; } sub in_range_of_influence { my ($self, $star) = @_; - if ($self->calculate_distance_to_target($star) > $self->range_of_influence) { + # measure influence not from the station, but from its star. + if ($self->star->calculate_distance_to_target($star) > $self->range_of_influence) { return; } return 1; } + +sub update_influence { + my $self = shift; + my $opts = shift || {}; + + my $range = $self->range_of_influence() / 100; + my $influence = $self->total_influence; + my $centerpoint = $self->star; + + # if the station is in NZ or SZ, it exerts no influence, period. + if ($centerpoint->in_neutral_area() or $centerpoint->in_starter_zone()) + { + # delete any influence it may have. + my $si = Lacuna->db->resultset('StationInfluence')->search({station_id => $self->id})->delete; + return; + } + + my $calc_influence = sub { + int( $influence * $influence * 23 / + ($_[0] * $_[0] + 0.01) # distance can be zero for same-star, so avoid divide-by-zero + ); + }; + + # find any stars influenced outside of our range. + # We can't simply delete directly because there's a trigger to update + # the star table, and we're already using the star table. + # Thus, grab the influence IDs affected, then we can delete them. + my $dbh = Lacuna->db->storage->dbh; + my $sth = $dbh->prepare_cached(<<'EOSQL'); + SELECT ID + FROM stationinfluence si + WHERE + si.station_id = ? + AND + exists (SELECT 1 + FROM star s + WHERE + s.id = si.star_id + AND + pow(pow(x - ?, 2) + pow(y - ?, 2), 0.5) > ? + ) +EOSQL + $sth->execute( + $self->id, + $centerpoint->x, $centerpoint->y, #,,, + $range + ); + my $all_ids = $sth->fetchall_arrayref(); + + # any deletions? + if (@$all_ids) + { + my $si = Lacuna->db->resultset('StationInfluence'); + $si->search({ id => { -in => [ map { @$_ } @$all_ids ] } })->delete; + } + + # look for any stars we are influencing that have changed influence. + # (one of the influence buildings have changed strength) + my $home_star = Lacuna->db->resultset('StationInfluence')->find({station_id => $self->id, star_id => $self->star_id}); + if (!$home_star || $calc_influence->(0) != $home_star->influence ) + { + my $influence_rs = Lacuna->db->resultset('StationInfluence')-> + search( + { 'me.station_id' => $self->id }, + { + join => 'star', + '+select' => [ + \[ 'pow(pow(star.x - ?, 2) + pow(star.y - ?, 2), 0.5) as distance', $centerpoint->x, $centerpoint->y, ], #, + ], + '+as' => [ 'distance' ], + } + ); + while (my $inf = $influence_rs->next) + { + $inf->influence($calc_influence->($inf->get_column('distance'))); + $inf->oldstart(DateTime->now()); + $inf->update; + } + } + + # now, look for any new stars we can influence. + my $stars_rs = Lacuna->db->resultset('Map::Star')->search({ + -and => [ + \[ 'pow(pow(me.x - ?, 2) + pow(me.y - ?, 2), 0.5) < ?', $centerpoint->x, $centerpoint->y, $range ], #,, + \[ 'NOT EXISTS (SELECT 1 FROM stationinfluence si WHERE si.station_id = ? AND me.id = si.star_id)', $self->id ], + ], + },{ + '+select' => [ + \[ 'pow(pow(me.x - ?, 2) + pow(me.y - ?, 2), 0.5) as distance', $centerpoint->x, $centerpoint->y, ], #, + ], + '+as' => [ 'distance' ], + order_by => 'distance', + }); + + $|++; +print "\n"; + + while (my $star = $stars_rs->next) + { + next if $star->in_neutral_area() or $star->in_starter_zone(); + + my $distance = $star->get_column('distance'); + my $target_influence = $calc_influence->($distance); + + printf "%-9d\r", $target_influence; + + # anything under MINIMUM_EXERTABLE_INFLUENCE is too weak to help/hinder influence of anything + # (also keeps the influence table from blowing up over all the IBS30s) + if ($target_influence > MINIMUM_EXERTABLE_INFLUENCE) + { + my $inf = Lacuna->db->resultset('StationInfluence')-> + create( + { + station_id => $self->id, + star_id => $star->id, + alliance_id => $self->alliance_id, + oldinfluence => 0, # start out with no influence + oldstart => DateTime->now, + started_influence => DateTime->now, + influence => $target_influence, + } + ); + $inf->update; + } + else + { + last; + } + } + +print "\n"; +} + no Moose; __PACKAGE__->meta->make_immutable(inline_constructor => 0); diff --git a/lib/Lacuna/DB/Result/Map/Star.pm b/lib/Lacuna/DB/Result/Map/Star.pm index b7acec51..fc170cc7 100644 --- a/lib/Lacuna/DB/Result/Map/Star.pm +++ b/lib/Lacuna/DB/Result/Map/Star.pm @@ -10,6 +10,8 @@ __PACKAGE__->table('star'); __PACKAGE__->add_columns( color => { data_type => 'varchar', size => 7, is_nullable => 0 }, station_id => { data_type => 'int', is_nullable => 1 }, + influence => { data_type => 'int', is_nullable => 1 }, + needs_recalc => { data_type => 'tinyint', is_nullable => 0, default_value => 0 }, ); __PACKAGE__->has_many('bodies', 'Lacuna::DB::Result::Map::Body', 'star_id'); @@ -17,6 +19,12 @@ __PACKAGE__->has_many('probes', 'Lacuna::DB::Result::Probes', 'star_id'); __PACKAGE__->has_many('laws', 'Lacuna::DB::Result::Laws', 'star_id'); __PACKAGE__->belongs_to('station', 'Lacuna::DB::Result::Map::Body', 'station_id', { on_delete => 'set null' }); +sub sqlt_deploy_hook { + my ($self, $sqlt_table) = @_; + my $schema = $sqlt_table->schema; + $sqlt_table->add_index(name => 'idx_recalc', fields => ['needs_recalc']); +} + sub send_predefined_message { my ($self, %options) = @_; my $members = $self->bodies->search({empire_id => {'!=' => undef } }); @@ -35,6 +43,7 @@ sub get_status_lite { x => $self->x, y => $self->y, zone => $self->zone, + influence => $self->influence, }; if ($self->station_id) { my $station = $self->station; @@ -72,6 +81,7 @@ sub get_status { x => $self->x, y => $self->y, zone => $self->zone, + influence => $self->influence, }; if (defined $empire) { if ($override_probe || $self->id ~~ $empire->probed_stars) { @@ -95,8 +105,68 @@ sub get_status { return $out; } +#,}}} +sub recalc_influence { + my ($self) = @_; + + if ($self->in_neutral_area or $self->in_starter_zone) { + # this should never really happen. + $self->needs_recalc(0); + $self->influence(0); + $self->station_id(undef); + $self->update; + return; + } + + # We will use some 'raw' SQL rather than go through the machinations of DBIC + # + my $dbh = $self->result_source->storage->dbh; + my ($alliances) = $dbh->selectrow_array('select count(distinct(alliance_id)) from stationinfluence where star_id = ?', undef, $self->id) or die $dbh->errstr; + my $station_id; + my $influence = 0; + + if ($alliances == 0) + { + # Do nothing, star is not under the influence of any alliance + } + else { + my $rs = Lacuna->db->resultset('StationInfluence'); + my $inf = $rs->search( + { star_id => $self->id }, + { + select => [ + 'alliance_id', + { sum => \[ $rs->sql_currentinfluence ], -as => 'totalinfluence' } + ], + as => [ qw/alliance_id totalinfluence/ ], + group_by => 'alliance_id', + order_by => { -desc => 'totalinfluence' }, + }); + my $best = $inf->next(); + $influence = $best->get_column('totalinfluence'); + while (my $i = $inf->next()) + { + $influence -= $i->get_column('totalinfluence'); + } + if ($influence > 100) + { + # and now we have to figure out which station is the strongest. + my $row = $rs->with_currentinfluence->find({ star_id => $self->id, alliance_id => $best->alliance_id },{order_by => {-desc => 'currentinfluence'}, limit => 1}); + $station_id = $row->station_id; + } + else + { + # not enough to overcome competition, so not owned + $influence = 0; + } + } + $self->needs_recalc(0); + $self->influence($influence); + $self->station_id($station_id); + $self->update; +} no Moose; __PACKAGE__->meta->make_immutable(inline_constructor => 0); diff --git a/lib/Lacuna/DB/Result/Map/StarLite.pm b/lib/Lacuna/DB/Result/Map/StarLite.pm index b05771b6..ae70c025 100644 --- a/lib/Lacuna/DB/Result/Map/StarLite.pm +++ b/lib/Lacuna/DB/Result/Map/StarLite.pm @@ -22,6 +22,7 @@ __PACKAGE__->add_columns( star_y => { data_type => 'int', size => 11, default_value => 0 }, star_zone => { data_type => 'varchar', size => 16, is_nullable => 0 }, station_id => { data_type => 'int', size => 11}, + influence => { data_type => 'int', size => 11}, body_id => { data_type => 'int', size => 11}, body_name => { data_type => 'varchar', size => 30, is_nullable => 0 }, body_orbit => { data_type => 'int', default_value => 0 }, @@ -47,6 +48,7 @@ __PACKAGE__->result_source_instance->view_definition(q[ star.y AS star_y, star.zone as star_zone, star.station_id AS station_id, + star.influence AS influence, body.id AS body_id, body.name AS body_name, body.orbit AS body_orbit, diff --git a/lib/Lacuna/DB/Result/Propositions.pm b/lib/Lacuna/DB/Result/Propositions.pm index be3ee470..e365134d 100644 --- a/lib/Lacuna/DB/Result/Propositions.pm +++ b/lib/Lacuna/DB/Result/Propositions.pm @@ -47,7 +47,6 @@ __PACKAGE__->typecast_map(type => { RenameUninhabited => 'Lacuna::DB::Result::Propositions::RenameUninhabited', RepairModule => 'Lacuna::DB::Result::Propositions::RepairModule', RepealLaw => 'Lacuna::DB::Result::Propositions::RepealLaw', - SeizeStar => 'Lacuna::DB::Result::Propositions::SeizeStar', Taxation => 'Lacuna::DB::Result::Propositions::Taxation', TransferStationOwnership=> 'Lacuna::DB::Result::Propositions::TransferStationOwnership', UpgradeModule => 'Lacuna::DB::Result::Propositions::UpgradeModule', diff --git a/lib/Lacuna/DB/Result/Propositions/FireBfg.pm b/lib/Lacuna/DB/Result/Propositions/FireBfg.pm index 88fff8b9..66e4225c 100644 --- a/lib/Lacuna/DB/Result/Propositions/FireBfg.pm +++ b/lib/Lacuna/DB/Result/Propositions/FireBfg.pm @@ -61,8 +61,6 @@ before pass => sub { } else { - # this should never happen: BFGs simply cannot pass - # if the parliament goes below 7 - nothing is still seized. $self->station->add_news(99, sprintf('The parliament of %s suffered a critical malfunction today while trying to fire their BFG.', $self->station->name)); } $self->station->update; diff --git a/lib/Lacuna/DB/Result/Propositions/RenameStar.pm b/lib/Lacuna/DB/Result/Propositions/RenameStar.pm index 2f5c10f8..d077fbd9 100644 --- a/lib/Lacuna/DB/Result/Propositions/RenameStar.pm +++ b/lib/Lacuna/DB/Result/Propositions/RenameStar.pm @@ -19,25 +19,6 @@ before pass => sub { else { $star->name($name); $star->update; - my $elaw = $station->laws->search({type => 'Jurisdiction', star_id => $star->id})->first; - if ($elaw) { - $elaw->name('Seize '.$name); - $elaw->description('Seize control of {Starmap '.$star->x.' '.$star->y.' '.$name.'} by {Planet '.$station->id.' '. - $station->name.'}, and apply all present laws to said star and its inhabitants.'); - $elaw->update; - } - else { - my $law = Lacuna->db->resultset('Lacuna::DB::Result::Laws')->new({ - name => 'Seize '.$name, - description => 'Seize control of {Starmap '.$star->x.' '.$star->y.' '.$name.'} by {Planet '.$station->id.' '. - $station->name.'}, and apply all present laws to said star and its inhabitants.', - type => 'Jurisdiction', - station_id => $station->id, - star_id => $star->id, - }); - $law->star($star); - $law->insert; - } } }; diff --git a/lib/Lacuna/DB/Result/Propositions/SeizeStar.pm b/lib/Lacuna/DB/Result/Propositions/SeizeStar.pm deleted file mode 100644 index 7313387d..00000000 --- a/lib/Lacuna/DB/Result/Propositions/SeizeStar.pm +++ /dev/null @@ -1,41 +0,0 @@ -package Lacuna::DB::Result::Propositions::SeizeStar; - -use Moose; -use utf8; -no warnings qw(uninitialized); -extends 'Lacuna::DB::Result::Propositions'; - -before pass => sub { - my ($self) = @_; - my $station = $self->station; - my $star = Lacuna->db->resultset('Lacuna::DB::Result::Map::Star')->find($self->scratch->{star_id}); - if ($star->in_starter_zone) { - $self->pass_extra_message('This star is in a starter zone and cannot be seized.'); - } - elsif ($star->in_neutral_area) { - $self->pass_extra_message('This star is in the neutral area and cannot be seized.'); - } - elsif (!$star->station_id) { - my $influence_remaining = $station->influence_remaining; - if ( $influence_remaining >= 1 ) { - my $law = Lacuna->db->resultset('Lacuna::DB::Result::Laws')->new({ - name => $self->name, - description => $self->description, - type => 'Jurisdiction', - station_id => $self->station_id, - star_id => $star->id, - }); - $law->star($star); - $law->insert; - } - else { - $self->pass_extra_message('Unfortunately, by the time the proposition passed, the station lacked any spare influence, effectively nullifying the vote.'); - } - } - else { - $self->pass_extra_message('Unfortunately, by the time the proposition passed, the star was already controlled by another station, effectively nullifying the vote.'); - } -}; - -no Moose; -__PACKAGE__->meta->make_immutable(inline_constructor => 0); diff --git a/lib/Lacuna/DB/Result/StationInfluence.pm b/lib/Lacuna/DB/Result/StationInfluence.pm new file mode 100644 index 00000000..323de271 --- /dev/null +++ b/lib/Lacuna/DB/Result/StationInfluence.pm @@ -0,0 +1,63 @@ +package Lacuna::DB::Result::StationInfluence; + +use Moose; +use utf8; +extends 'Lacuna::DB::Result'; +use POSIX qw(ceil); + +__PACKAGE__->table('stationinfluence'); +__PACKAGE__->add_columns( + station_id => { data_type => 'int', is_nullable => 0 }, + star_id => { data_type => 'int', is_nullable => 0 }, + alliance_id => { data_type => 'int', is_nullable => 0 }, + oldinfluence => { data_type => 'int', is_nullable => 0, default => 0 }, + oldstart => { data_type => 'datetime', is_nullable => 0 }, + influence => { data_type => 'int', is_nullable => 0, default => 0 }, + started_influence => { data_type => 'datetime', is_nullable => 0 }, +); + +__PACKAGE__->belongs_to('station', 'Lacuna::DB::Result::Map::Body', 'station_id', { on_delete => 'cascade' }); +__PACKAGE__->belongs_to('star', 'Lacuna::DB::Result::Map::Star', 'star_id'); +__PACKAGE__->belongs_to('alliance', 'Lacuna::DB::Result::Alliance', 'alliance_id'); + +sub sqlt_deploy_hook { + my ($self, $sqlt_table) = @_; + my $schema = $sqlt_table->schema; + + # when we clear rows here, ensure we set the original star for cleanup. + $schema->add_trigger( + name => 'stationinfluence_cleanup', + perform_action_when => 'after', + database_events => [qw/delete/], + on_table => 'stationinfluence', + scope => 'row', + action => q[UPDATE star s SET s.needs_recalc = 1 WHERE s.id = OLD.star_id;], + ); +} + +# try to keep this up to date with the one in SQL in ResultSet +sub current_influence { + my ($self) = @_; + + return 0 if $self->star->in_neutral_area or $self->star->in_starter_zone; + + return $self->influence if $self->oldstart < DateTime->now->subtract(seconds => 24 * 60 * 60); + + my $now = DateTime->now->set_time_zone('GMT'); + my $sec = $now->subtract_datetime_absolute($self->oldstart->set_time_zone('GMT'))->seconds(); + ceil( + $self->oldinfluence + + ($self->influence - $self->oldinfluence) * ($sec / (24 * 60 * 60)) + ); +} + +sub currentinfluence { + my ($self) = @_; + + # either we got it from the query, or we calculate it ourselves. + eval { $self->get_column('currentinfluence') } // $self->current_influence; +} + +no Moose; +__PACKAGE__->meta->make_immutable(inline_constructor => 0); +1; diff --git a/lib/Lacuna/DB/ResultSet/Map/Star.pm b/lib/Lacuna/DB/ResultSet/Map/Star.pm new file mode 100644 index 00000000..65b544d4 --- /dev/null +++ b/lib/Lacuna/DB/ResultSet/Map/Star.pm @@ -0,0 +1,34 @@ +package Lacuna::DB::ResultSet::Map::Star; + +use Moose; +use utf8; +no warnings qw(uninitialized); +#use Lacuna; + +extends 'Lacuna::DB::ResultSet'; + +sub recalc_control { + my ($self) = @_; + + # first look through any stars whose influence was changed in the last 26 hours. + my $db = Lacuna->db; + my $dtf = $db->storage->datetime_parser; + + my $too_new = $dtf->format_datetime(DateTime->now->subtract(hours => 26)); + my $stars = $db->resultset('StationInfluence')-> + search({ oldstart => { '>' => $too_new }, }, { select => { distinct => 'star_id'}, as => 'star_id' }); + while (my $s = $stars->next) + { + Lacuna->db->resultset('Map::Star')->find($s->star_id)->recalc_influence; + } + + # are there any stars still in need of recalc (e.g., for deletes)? + $stars = $self->search({ needs_recalc => 1 }, {}); + while (my $star = $stars->next) + { + $star->recalc_influence; + } +} + +1; + diff --git a/lib/Lacuna/DB/ResultSet/Map/StarLite.pm b/lib/Lacuna/DB/ResultSet/Map/StarLite.pm index 1b761062..49abf400 100644 --- a/lib/Lacuna/DB/ResultSet/Map/StarLite.pm +++ b/lib/Lacuna/DB/ResultSet/Map/StarLite.pm @@ -33,6 +33,7 @@ sub get_star_map { y => $row->star_y, id => $row->star_id, zone => $row->star_zone, + influence => $row->influence, }; $star_id = $row->star_id; diff --git a/lib/Lacuna/DB/ResultSet/StationInfluence.pm b/lib/Lacuna/DB/ResultSet/StationInfluence.pm new file mode 100644 index 00000000..91dcda3c --- /dev/null +++ b/lib/Lacuna/DB/ResultSet/StationInfluence.pm @@ -0,0 +1,27 @@ +package Lacuna::DB::ResultSet::StationInfluence; + +use Moose; +use utf8; +no warnings qw(uninitialized); +#use Lacuna; + +extends 'Lacuna::DB::ResultSet'; + +# try to keep this up to date with the one in perl in Result +sub sql_currentinfluence() { + 'CASE WHEN TIMESTAMPADD(HOUR,24,me.oldstart) < UTC_TIMESTAMP() THEN me.influence ELSE CEIL(me.oldinfluence + (me.influence - me.oldinfluence) * timestampdiff(second,me.oldstart,UTC_TIMESTAMP()) / (24 * 60 * 60)) END' +} + +sub with_currentinfluence +{ + my ($self) = @_; + $self->search( + {}, + { + '+select' => \[ sql_currentinfluence . ' as currentinfluence' ], + '+as' => 'currentinfluence' + }); +} + +1; + diff --git a/lib/Lacuna/RPC/Building/BlackHoleGenerator.pm b/lib/Lacuna/RPC/Building/BlackHoleGenerator.pm index 6a2d289a..e25eefe2 100644 --- a/lib/Lacuna/RPC/Building/BlackHoleGenerator.pm +++ b/lib/Lacuna/RPC/Building/BlackHoleGenerator.pm @@ -1445,12 +1445,7 @@ sub drop_stars_beyond_range { my ($station) = @_; return 0 if ($station->get_type ne 'space station'); - my $laws = $station->laws->search({type => 'Jurisdiction'}); - while (my $law = $laws->next) { - unless ($station->in_range_of_influence($law->star)) { - $law->delete; - } - } + $station->update_influence(); return 1; } diff --git a/lib/Lacuna/RPC/Building/Parliament.pm b/lib/Lacuna/RPC/Building/Parliament.pm index 43861168..d45d231a 100644 --- a/lib/Lacuna/RPC/Building/Parliament.pm +++ b/lib/Lacuna/RPC/Building/Parliament.pm @@ -289,63 +289,6 @@ sub propose_transfer_station_ownership { }; } -sub propose_seize_star { - my ($self, $session_id, $building_id, $star_id) = @_; - my $empire = $self->get_empire_by_session($session_id); - if ($empire->current_session->is_sitter) { - confess [1015, 'Sitters cannot create propositions.']; - } - my $building = $self->get_building($empire, $building_id); - unless ($building->effective_level >= 7) { - confess [1013, 'Parliament must be level 7 to seize control of a star.',7]; - } - unless ($building->effective_level > 0 and $building->effective_efficiency == 100) { - confess [1003, "You must have a functional Parliament!"]; - } - unless ($star_id) { - confess [1002, 'Must specify a star id to seize.']; - } - if ($building->body->in_neutral_area) { - confess [1009, 'Your station is in the Neutral Area and is not allowed to seize any star.']; - } - if ($building->body->in_starter_zone) { - confess [1009, 'Your station is in a Starter Zone and is not allowed to seize any star.']; - } - my $star = Lacuna->db->resultset('Lacuna::DB::Result::Map::Star')->find($star_id); - unless (defined $star) { - confess [1002, 'Could not find the star.']; - } - if ($star->in_neutral_area) { - confess [1009, 'That star is in the Neutral Area and can not be seized.']; - } - if ($star->in_starter_zone) { - confess [1009, 'That star is in a Starter Zone and can not be seized.']; - } - if ($star->station_id) { - confess [1009, 'That star is already controlled by a station.']; - } - unless ($building->body->influence_remaining > 0) { - confess [1009, 'You do not have enough influence to control another star.']; - } - unless ($building->body->in_range_of_influence($star)) { - confess [1009, 'That star is not in range of influence.']; - } - my $proposition = Lacuna->db->resultset('Lacuna::DB::Result::Propositions')->new({ - type => 'SeizeStar', - name => 'Seize '.$star->name, - description => 'Seize control of {Starmap '.$star->x.' '.$star->y.' '.$star->name.'} by {Planet '.$building->body->id.' '.$building->body->name.'}, and apply all present laws to said star and its inhabitants.', - scratch => { star_id => $star->id }, - proposed_by_id => $empire->id, - }); - $proposition->station($building->body); - $proposition->proposed_by($empire); - $proposition->insert; - return { - status => $self->format_status($empire, $building->body), - proposition => $proposition->get_status($empire), - }; -} - sub propose_repeal_law { my ($self, $session_id, $building_id, $law_id) = @_; my $empire = $self->get_empire_by_session($session_id); @@ -987,7 +930,7 @@ sub view_taxes_collected { -__PACKAGE__->register_rpc_method_names(qw(get_bodies_for_star_in_jurisdiction get_mining_platforms_for_asteroid_in_jurisdiction propose_evict_mining_platform propose_members_only_mining_rights propose_members_only_colonization propose_rename_asteroid propose_rename_uninhabited propose_broadcast_on_network19 get_stars_in_jurisdiction propose_rename_star propose_repeal_law propose_seize_star propose_transfer_station_ownership view_propositions view_laws cast_vote propose_fire_bfg propose_writ propose_elect_new_leader propose_induct_member propose_expel_member propose_taxation view_taxes_collected propose_foreign_aid propose_evict_excavator propose_members_only_excavation propose_neutralize_bhg)); +__PACKAGE__->register_rpc_method_names(qw(get_bodies_for_star_in_jurisdiction get_mining_platforms_for_asteroid_in_jurisdiction propose_evict_mining_platform propose_members_only_mining_rights propose_members_only_colonization propose_rename_asteroid propose_rename_uninhabited propose_broadcast_on_network19 get_stars_in_jurisdiction propose_rename_star propose_repeal_law propose_transfer_station_ownership view_propositions view_laws cast_vote propose_fire_bfg propose_writ propose_elect_new_leader propose_induct_member propose_expel_member propose_taxation view_taxes_collected propose_foreign_aid propose_evict_excavator propose_members_only_excavation propose_neutralize_bhg)); no Moose; __PACKAGE__->meta->make_immutable; diff --git a/lib/Lacuna/Role/Influencer.pm b/lib/Lacuna/Role/Influencer.pm index 349167c7..3e98c9b1 100644 --- a/lib/Lacuna/Role/Influencer.pm +++ b/lib/Lacuna/Role/Influencer.pm @@ -4,53 +4,17 @@ use Moose::Role; before demolish => sub { my $self = shift; - my $station = $self->body; - my $influence_remaining = $station->influence_remaining - $self->effective_level; - my $laws = $station->laws->search({type => 'Jurisdiction'},{order_by => { -desc => 'date_enacted'}}); - while (my $law = $laws->next) { - last if $influence_remaining >= 0; - $law->delete; - $influence_remaining++; - } + $self->body->update_influence; }; before downgrade => sub { my $self = shift; - my $station = $self->body; - - my @laws = $station->laws->search({type => 'Jurisdiction'},{order_by => { -desc => 'date_enacted'}})->all; - - # If this is an IBS, we must remove soon-to-be out of range star seizures now instead - # of after downgrade, since that may make removing any others unnecessary and improper - if ($self->isa('Lacuna::DB::Result::Building::Module::IBS')) { - my $range_of_influence = $station->range_of_influence - 1000; - for (my $law_index = $#laws; $law_index >= 0; --$law_index ) { - my $law = $laws[$law_index]; - if ($station->calculate_distance_to_target($law->star) > $range_of_influence) { - $law->delete; - splice @laws, $law_index, 1; - } - } - } - - my $influence_remaining = $station->influence_remaining - 1; - for my $law (@laws) { - last if $influence_remaining >= 0; - $law->delete; - $influence_remaining++; - } + $self->body->update_influence; }; after finish_upgrade => sub { my $self = shift; - my $station = $self->body; - my $influence_remaining = $station->influence_remaining; - my $laws = $station->laws->search({type => 'Jurisdiction'},{order_by => { -desc => 'date_enacted'}}); - while (my $law = $laws->next) { - last if $influence_remaining >= 0; - $law->delete; - $influence_remaining++; - } + $self->body->update_influence; }; 1; diff --git a/setup_a_server.txt b/setup_a_server.txt index e21bacba..0f7184a2 100644 --- a/setup_a_server.txt +++ b/setup_a_server.txt @@ -78,11 +78,26 @@ no longer supported. Check the web sites for the closest version to use. Step 2: Start Storage -You need to start up you MySQL server and memcached. Memcached is as easy as: +You need to start up your MySQL server, memcached, and beanstalk. -memcached -d -MySQL will depend on the system and how you installed it. +Memcached is as easy as: + +memcached -d -u nobody -m 512 + +For a private server, 512 may be overkill, -m 64, the default, is +likely sufficient. + +For beanstalk, see the setup_beanstalk.txt file. At this point, only +the setup is required, the scheduler is not. + + +MySQL needs one extra bit of configuration. In /etc/my.cnf, find the +section labelled "[mysqld]" and add the following line: + + log_bin_trust_function_creators = 1 + +Starting MySQL will depend on the system and how you installed it. # Make sure MySQL service starts on boot [root@myserver /]# chkconfig --levels 235 mysqld on diff --git a/t/220_Parliament.t b/t/220_Parliament.t index b7315a30..33f42d7c 100644 --- a/t/220_Parliament.t +++ b/t/220_Parliament.t @@ -104,15 +104,9 @@ is($result->{error}{code}, 1009, 'cannot transfer a station to a non-alliance me $result = $tester->post('parliament', 'propose_transfer_station_ownership', [$session_id, $par->id, $friend->empire->id]); is($result->{error}{code}, 1013, 'cannot transfer a station to an isolationist'); -$result = $tester->post('parliament', 'propose_seize_star', [$session_id, $par->id]); -is($result->{error}{data}, 7, 'seizing star requires level 7 parliament'); - $par->level(7); $par->update; -$result = $tester->post('parliament', 'propose_seize_star', [$session_id, $par->id, $friend->empire->home_planet->star_id]); -is($result->{error}{code}, 1009, 'star is not in range of influence'); - $result = $tester->post('parliament', 'propose_rename_star', [$session_id, $par->id]); is($result->{error}{data}, 8, 'renaming star requires level 8 parliament'); diff --git a/t/480_parliament_stars.t b/t/480_parliament_stars.t index 55ece69f..c056a6a6 100644 --- a/t/480_parliament_stars.t +++ b/t/480_parliament_stars.t @@ -37,43 +37,6 @@ while( my $star = $stars_rs->next ) { # TODO Call get_stars_in_jurisdiction and check sort order -my $influence_spent = $station->influence_spent; -my $influence_remaining = $station->influence_remaining; - -# get unseized stars in order distance from station. -my $minus_x = 0 - $station->x; -my $minus_y = 0 - $station->y; - -my $closest = Lacuna->db->resultset('Map::Star')->search({ - station_id => undef, -},{ - '+select' => [ - { ceil => \"pow(pow(me.x + $minus_x,2) + pow(me.y + $minus_y,2), 0.5)", '-as' => 'distance' }, - ], - '+as' => [ - 'distance', - ], - order_by => 'distance', -}); - -# Test setting seize propositions -while ($influence_remaining) { - my $star = $closest->next; - - diag("Propose seizing\t".$star->x."\t".$star->y."\t ".$star->name); - my $proposition = Lacuna->db->resultset('Lacuna::DB::Result::Propositions')->new({ - type => 'SeizeStar', - name => 'Seize '.$star->name, - description => 'Seize control of {Starmap '.$star->x.' '.$star->y.' '.$star->name.'} by {Planet '.$station->id.' '.$station->name.'}, and apply all present laws to said star and its inhabitants.', - scratch => { star_id => $star->id }, - proposed_by_id => $empire->id, - }); - $proposition->station($station); - $proposition->proposed_by($empire); - $proposition->insert; - $influence_remaining--; -} - # TODO check that we can vote on propositions. # TODO check that stars are in order. # TODO check that propositions are in order. diff --git a/var/upgrades/3.0909.sql b/var/upgrades/3.0909.sql index bbc2cac1..354cba23 100644 --- a/var/upgrades/3.0909.sql +++ b/var/upgrades/3.0909.sql @@ -7,6 +7,11 @@ alter table empire add column timeout tinyint default 0; alter table empire add column outlaw tinyint default 0; alter table empire add column outlaw_date datetime default '2010-10-03 18:17:26'; alter table essentia_log modify transaction_id varchar(36); +alter table star add column needs_recalc tinyint not null default 0; +alter table star add column influence int not null default 0; +alter table star add index idx_recalc (needs_recalc); + +delete from laws where type = 'Jurisdiction'; CREATE TABLE empire_admin_notes ( id integer(11) NOT NULL auto_increment, @@ -19,3 +24,30 @@ CREATE TABLE empire_admin_notes ( INDEX idx_empire_name (empire_name), PRIMARY KEY (id) ); + +CREATE TABLE stationinfluence ( + id integer(11) NOT NULL auto_increment, + station_id integer NOT NULL, + star_id integer NOT NULL, + alliance_id integer NOT NULL, + oldinfluence integer NOT NULL, + oldstart datetime NULL, + influence integer NOT NULL, + started_influence datetime NOT NULL, + INDEX stationinfluence_idx_alliance_id (alliance_id), + INDEX stationinfluence_idx_star_id (star_id), + INDEX stationinfluence_idx_station_id (station_id), + PRIMARY KEY (id), + CONSTRAINT stationinfluence_fk_alliance_id FOREIGN KEY (alliance_id) REFERENCES alliance (id), + CONSTRAINT stationinfluence_fk_star_id FOREIGN KEY (star_id) REFERENCES star (id), + CONSTRAINT stationinfluence_fk_station_id FOREIGN KEY (station_id) REFERENCES body (id) ON DELETE CASCADE +); + +DELIMITER // + +CREATE TRIGGER stationinfluence_cleanup after delete ON stationinfluence + FOR EACH ROW BEGIN + UPDATE star s SET s.needs_recalc = 1 WHERE s.id = OLD.star_id; +END // + +DELIMITER ;