diff --git a/bin/handle_espionage.pl b/bin/handle_espionage.pl new file mode 100644 index 00000000..b0acc6a6 --- /dev/null +++ b/bin/handle_espionage.pl @@ -0,0 +1,269 @@ +use 5.010; +use lib '/data/Lacuna-Server/lib'; +use Lacuna::DB; +use Lacuna; +use List::Util qw(shuffle); + +my $config = Lacuna->config; +my $age = DateTime->now->subtract(hours=>24); +my $db = Lacuna::DB->new( access_key => $config->get('access_key'), secret_key => $config->get('secret_key'), cache_servers => $config->get('memcached')); + +my $planets = $db->domain('Lacuna::DB::Body::Planet')->search( + where => { + empire_id => ['!=', 'None'], + } +); + +while (my $planet = $planets->next) { + ship_report($planet); + travel_report($planet); + economic_report($planet); + colony_report($planet); + spy_report($planet); + interrogation_report($planet); + surface_report($planet); + hack_local_probes($planet); + hack_network19($planet); + hack_observatory_probes($planet); + incite_rebellion($planet); + turn_spy($planet); +} + +sub random_spy { + my $spies = shift; + my @random = shuffle @{$spies}; + return $random[0]; +} + +sub travel_report { + my $planet = shift; + my $spy = random_spy($planet->investigators); + return undef if ($spy->empire_id eq $planet->empire_id); + if ($planet->check_intel) { + my @travelling = (['From','To','Type']); + my $ships = $planet->ships_travelling; + my $got; + while (my $ship = $ships->next) { + my $target = ($ship->foreign_body_id) ? $ship->foreign_body : $ship->foreign_star; + my $from = $body->name; + my $to = $target->name; + if ($ship->direction ne 'outgoing') { + my $temp = $from; + $from = $to; + $to = $temp; + } + my $type = $ship->ship_type; + $type =~ s/_/ /g; + push @travelling, [ + $body->name, + $target->name, + ]; + $got = 1; + } + if ($got) { + $spy->empire->send_predefined_message( + tags => ['Alert'], + filename => 'intel_report.txt', + params => ['Travel Report', $spy->name], + attach_table=> \@travelling, + ); + } + } + else { + $planet->defeat_intel; + } +} + +sub colony_report { + my $planet = shift; + my $spy = random_spy($planet->investigators); + return undef if ($spy->empire_id eq $planet->empire_id); + if ($planet->check_intel) { + my @colonies = (['Name','X','Y','Z','Orbit']); + my $planets = $planet->empire->planets; + while (my $colony = $planets->next) { + push @colonies, [ + $colony->name, + $colony->x, + $colony->y, + $colony->z, + $colony->orbit, + ]; + } + $spy->empire->send_predefined_message( + tags => ['Alert'], + filename => 'intel_report.txt', + params => ['Colony Report', $spy->name], + attach_table=> \@colonies, + ); + } + else { + $planet->defeat_intel; + } +} + +sub ship_report { + my $planet = shift; + my $spy = random_spy($planet->investigators); + return undef if ($spy->empire_id eq $planet->empire_id); + if ($planet->check_intel) { + my $ports = $planet->get_buildings_of_class('Lacuna::DB::Building::SpacePort'); + my $got; + my %tally; + while (my $port = $ports->next) { + $got = 1; + $tally{probes} += $port->probe_count; + $tally{'colony ships'} += $port->colony_ship_count; + $tally{'spy pods'} += $port->spy_pod_count; + $tally{'cargo ships'} += $port->cargo_ship_count; + $tally{'space stations'} += $port->space_station_count; + $tally{'smuggler ships'} += $port->smuggler_ship_count; + $tally{'mining platform ships'} += $port->mining_platform_ship_count; + $tally{'terraforming platform ships'} += $port->terraforming_platform_ship_count; + $tally{'gas giant settlement platform ships'} += $port->gas_giant_settlement_platform_ship_count; + } + if ($got) { + my @ships = (['Type','Quantity']); + foreach my $ship (keys %tally) { + push @ships, [$ship, $tally{$ship}]; + } + $spy->empire->send_predefined_message( + tags => ['Alert'], + filename => 'intel_report.txt', + params => ['Ship Report', $spy->name], + attach_table=> \@ships, + ); + } + } + else { + $planet->defeat_intel; + } +} + + +sub economic_report { + my $planet = shift; + my $spy = random_spy($planet->investigators); + return undef if ($spy->empire_id eq $planet->empire_id); + if ($planet->check_intel) { + my @resources = (['Resource', 'Per Hour', 'Stored']); + push @resources, [ 'Food', $planet->food_hour, $planet->food_stored ]; + push @resources, [ 'Water', $planet->water_hour, $planet->water_stored ]; + push @resources, [ 'Energy', $planet->energy_hour, $planet->energy_stored ]; + push @resources, [ 'Ore', $planet->ore_hour, $planet->ore_stored ]; + push @resources, [ 'Waste', $planet->waste_hour, $planet->waste_stored ]; + $spy->empire->send_predefined_message( + tags => ['Alert'], + filename => 'intel_report.txt', + params => ['Economic Report', $spy->name], + attach_table=> \@resources, + ); + } + else { + $planet->defeat_intel; + } +} + +sub interrogation_report { + my $planet = shift; + my $interrogator = random_spy($planet->investigator); + return undef unless (defined $interrogator && $interrogator->empire_id eq $planet->empire_id); + my $suspect = $db->domain('spies')->search(where=>{on_body_id=>$planet->id, task=>'Captured', 'itemName()' => ['!=','None']}, order_by=>'itemName()')->next; + return undef unless (defined $suspect); + if ($interrogator->offense > $suspect->defense) { + my $suspect_home = $suspect->from_body; + my $suspect_empire = $suspect->empire; + my $suspect_species = $suspect_empire->species; + $interrogator->empire->send_predefined_message( + tags => ['Alert'], + filename => 'intel_report.txt', + params => ['Interrogation Report', $interrogator->name], + attach_table=> [ + ['Question', 'Response'], + ['Name', $suspect->name], + ['Offense Rating', $suspect->offense], + ['Defense Rating', $suspect->defense], + ['Allegiance', $suspect_empire->name], + ['Home World', $suspect_home->name], + ['Species Name', $suspect_species->name], + ['Species Description', $suspect_species->description], + ['Habitable Orbits', join(', ', @{$suspect_species->habitable_orbits})], + ['Manufacturing Affinity', $suspect_species->manufacturing_affinity], + ['Deception Affinity', $suspect_species->deception_affinity], + ['Research Affinity', $suspect_species->research_affinity], + ['Management Affinity', $suspect_species->management_affinity], + ['Farming Affinity', $suspect_species->farming_affinity], + ['Mining Affinity', $suspect_species->mining_affinity], + ['Science Affinity', $suspect_species->science_affinity], + ['Environmental Affinity', $suspect_species->environmental_affinity], + ['Political Affinity', $suspect_species->political_affinity], + ['Trade Affinity', $suspect_species->trade_affinity], + ['Growth Affinity', $suspect_species->growth_affinity], + ], + ); + } + else { + my $event = randint(1,100); + if ($event < 10) { + $interrogator->kill($planet); + $suspect->escape; + } + elsif ($event < 20) { + $suspect->escape; + } + elsif ($event < 30) { + $suspect->kill($planet); + } + } +} + +sub surface_report { + my $planet = shift; + my $spy = random_spy($planet->investigators); + return undef if ($spy->empire_id eq $planet->empire_id); + if ($planet->check_intel) { + my @map; + foreach my $buildings ($planet->buildings) { + while (my $building = $buildings->next) { + push @map, { + image => $building->image_level, + x => $building->x, + y => $building->y, + }; + } + } + $spy->empire->send_predefined_message( + tags => ['Alert'], + filename => 'intel_report.txt', + params => ['Surface Report', $spy->name], + attach_map => { + surface_image => $planet->surface, + buildings => \@map + }, + ); + } + else { + $planet->defeat_intel; + } +} + +sub spy_report { + my $planet = shift; + my $spy = random_spy($planet->investigators); + my @peeps = (['From','Assignment']); + my %planets = ( $planet->id => $planet->name ); + my $spy_list = $db->domain('spies')->search(where=>{ on_body_id => $planet->id, empire_id => ['!=', $spy->empire_id] }); + while (my $spy = $spy_list->next) { + unless (exists $planets{$spy->from_body_id}) { + $planets{$spy->from_body_id} = $spy->from_body->name; + } + push @peeps, [$planets{$spy->from_body_id}, $spy->task]; + } + $spy->empire->send_predefined_message( + tags => ['Alert'], + filename => 'intel_report.txt', + params => ['Counter Intelligence Report', $spy->name], + attach_table=> \@peeps, + ); +} + diff --git a/lib/Lacuna/DB/Body/Planet.pm b/lib/Lacuna/DB/Body/Planet.pm index cfd8325d..7d306ea3 100644 --- a/lib/Lacuna/DB/Body/Planet.pm +++ b/lib/Lacuna/DB/Body/Planet.pm @@ -149,11 +149,8 @@ has determine_espionage => ( lazy => 1, default => sub { my $self = shift; - my $steal = 0; - my $sabotage = 0; - my @thieves; - my @saboteurs; - my @interceptors; + my $steal = my $sabotage = my $interception = my $rebel = my $hack = my $intel = 0; + my (@thieves, @saboteurs, @interceptors, @spies, @hackers, @rebels); my $spies = $self->simpledb->domain('spies')->search( where => { on_body_id => $self->id, @@ -169,34 +166,48 @@ has determine_espionage => ( $steal += $spy->offense; push @thieves, $spy; } + elsif ($spy->task eq 'Gather Intelligence') { + $intel += $spy->offense; + push @spies, $spy; + } + elsif ($spy->task eq 'Incite Rebellion') { + $rebel += $spy->offense; + push @rebels, $spy; + } + elsif ($spy->task eq 'Hack Networks') { + $hack += $spy->offense; + push @hackers, $spy; + } elsif ($spy->task eq 'Capture Spies') { - $steal -= $spy->defense; - $sabotage -= $spy->defense; + $interception += $spy->defense; + push @interceptors, $spy; } } $self->thieves(\@thieves); - $self->saboteurs(\@saboteurs); - $self->interceptors(\@interceptors); $self->theft_score( $steal ); + $self->hackers(\@hackers); + $self->hack_score( $hack ); + $self->rebels(\@rebels); + $self->rebel_score( $rebel ); + $self->investigators(\@spies); + $self->intel_score( $intel ); + $self->saboteurs(\@saboteurs); $self->sabotage_score( $sabotage ); + $self->interceptors(\@interceptors); + $self->interception_score( $interception ); return 1; }, ); sub kill_a_spy { my ($self, $spy, $interceptor) = @_; - $spy->empire->send_predefined_message( - tags => ['Alert'], - filename => 'spy_killed.txt', - params => [$spy->name, $self->name], - ); $self->empire->send_predefined_message( tags => ['Alert'], filename => 'we_killed_a_spy.txt', params => [$self->name, $interceptor->name], from => $interceptor->empire, ); - $spy->delete; + $spy->kill($self); } sub capture_a_spy { @@ -282,6 +293,81 @@ sub defeat_sabotage { } } +sub defeat_rebellion { + my ($self) = @_; + if ($self->chance_of_rebellion > 0) { + my $event = randint(1,100); + my $spy = $self->rebels->[0]; + my $interceptor = $self->interceptors->[0]; + if ($event < 10) { + $self->rebel_score( $self->rebel_score - $spy->offense ); + $self->kill_a_spy($spy, $interceptor); + $self->add_news(80,'The leader of the rebellion to overthrow %s was killed in a firefight today on %s.', $self->empire->name, $self->name); + delete $self->rebels->[0]; + } + elsif ($event < 35) { + $self->rebel_score( $self->rebel_score - $spy->offense ); + $self->capture_a_spy($spy, $interceptor); + $self->add_news(50,'Police say they have crushed the rebellion on %s by apprehending %s.', $self->name, $spy->name); + delete $self->rebels->[0]; + } + else { + $self->miss_a_spy($spy, $interceptor); + $self->add_news(20,'The rebel leader, known as %s, is still eluding authorities on %s at this hour.', $spy->name, $self->name); + } + } +} + +sub defeat_hack { + my ($self) = @_; + if ($self->chance_of_hack > 0) { + my $event = randint(1,100); + my $spy = $self->hackers->[0]; + my $interceptor = $self->interceptors->[0]; + if ($event < 5) { + $self->hack_score( $self->hack_score - $spy->offense ); + $self->kill_a_spy($spy, $interceptor); + $self->add_news(60,'A suspected hacker, age '.randint(16,60).', was found dead in his home today on %s.', $self->name); + delete $self->hackers->[0]; + } + elsif ($event < 30) { + $self->hack_score( $self->hack_score - $spy->offense ); + $self->capture_a_spy($spy, $interceptor); + $self->add_news(30,'Alleged hacker %s is awaiting arraignment on %s today.', $spy->name, $self->name); + delete $self->hackers->[0]; + } + else { + $self->miss_a_spy($spy, $interceptor); + $self->add_news(10,'Identity theft has become a real problem on %s.', $self->name); + } + } +} + +sub defeat_intel { + my ($self) = @_; + if ($self->chance_of_intel > 0) { + my $event = randint(1,100); + my $spy = $self->investigators->[0]; + my $interceptor = $self->interceptors->[0]; + if ($event < 5) { + $self->intel_score( $self->intel_score - $spy->offense ); + $self->kill_a_spy($spy, $interceptor); + $self->add_news(60,'A suspected spy known only as %s was killed in a struggle with police on %s today.', $spy->name, $self->name); + delete $self->investigators->[0]; + } + elsif ($event < 15) { + $self->intel_score( $self->intel_score - $spy->offense ); + $self->capture_a_spy($spy, $interceptor); + $self->add_news(30,'An individual is behing held for questioning on %s at this hour for looking suspicious.', $self->name); + delete $self->investigators->[0]; + } + else { + $self->miss_a_spy($spy, $interceptor); + $self->add_news(10,'Corporate espionage has become a real problem on %s.', $self->name); + } + } +} + sub pick_a_spy_per_empire { my ($self, $spies) = @_; my %empires; @@ -319,7 +405,7 @@ sub chance_of_sabotage { my $self = shift; $self->determine_espionage; my $chance = $self->sabotage_score - $self->interception_score; - return ($chance > 90) ? 90 : $chance; + return ($chance > 80) ? 80 : $chance; } sub check_sabotage { @@ -337,6 +423,72 @@ has sabotage_score => ( default => 0, ); +sub chance_of_hack { + my $self = shift; + $self->determine_espionage; + my $chance = $self->hack_score - $self->interception_score; + return ($chance > 70) ? 70 : $chance; +} + +sub check_hack { + my $self = shift; + return ($self->chance_of_hack > randint(1,100)); +} + +has hackers => ( + is => 'rw', + default => sub { [] }, +); + +has hack_score => ( + is => 'rw', + default => 0, +); + +sub chance_of_rebellion { + my $self = shift; + $self->determine_espionage; + my $chance = $self->rebel_score - $self->interception_score; + return ($chance > 50) ? 50 : $chance; +} + +sub check_rebellion { + my $self = shift; + return ($self->chance_of_rebellion > randint(1,100)); +} + +has rebels => ( + is => 'rw', + default => sub { [] }, +); + +has rebel_score => ( + is => 'rw', + default => 0, +); + +sub chance_of_intel { + my $self = shift; + $self->determine_espionage; + my $chance = $self->intel_score - $self->interception_score; + return ($chance > 90) ? 90 : $chance; +} + +sub check_intel { + my $self = shift; + return ($self->chance_of_intel > randint(1,100)); +} + +has investigators => ( + is => 'rw', + default => sub { [] }, +); + +has intel_score => ( + is => 'rw', + default => 0, +); + has interception_score => ( is => 'rw', default => 0, diff --git a/lib/Lacuna/DB/Spies.pm b/lib/Lacuna/DB/Spies.pm index ef313429..df2264ed 100644 --- a/lib/Lacuna/DB/Spies.pm +++ b/lib/Lacuna/DB/Spies.pm @@ -61,6 +61,35 @@ sub assign { return $self; } +sub kill { + my ($self, $body); + $body ||= $self->on_body; + $self->empire->send_predefined_message( + tags => ['Alert'], + filename => 'spy_killed.txt', + params => [$self->name, $body->name], + ); + $self->delete; +} + +sub escape { + my ($self, $body); + $self->available_on(DateTime->now); + $self->task('Idle'); + $self->put; + my $evil_empire = $self->on_body->empire; + $self->empire->send_predefined_message( + tags => ['Alert'], + filename => 'i_have_escaped.txt', + params => [$evil_empire->name, $self->name], + ); + $evil_empire->send_predefined_message( + tags => ['Alert'], + filename => 'you_cant_hold_me.txt', + params => [$self->name], + ); +} + sub steal_a_building { my ($self, $building) = @_; my $body = $building->body; diff --git a/var/messages/i_have_escaped.txt b/var/messages/i_have_escaped.txt new file mode 100644 index 00000000..0b36f4b3 --- /dev/null +++ b/var/messages/i_have_escaped.txt @@ -0,0 +1,5 @@ +I'm Back! +~~~ +I managed to break out of the %s prison. I'm ready for duty, put me to work. + +%s diff --git a/var/messages/intel_report.txt b/var/messages/intel_report.txt new file mode 100644 index 00000000..3f1633c9 --- /dev/null +++ b/var/messages/intel_report.txt @@ -0,0 +1,7 @@ +%s +~~~ +Here's some information you may find interesting. + +More to come, + +%s diff --git a/var/messages/you_cant_hold_me.txt b/var/messages/you_cant_hold_me.txt new file mode 100644 index 00000000..d0e244d2 --- /dev/null +++ b/var/messages/you_cant_hold_me.txt @@ -0,0 +1,5 @@ +You Can't Hold Me! +~~~ +I don't know why I ever let you capture me in the first place. The incompetence of your agents must give you an ulcer. + +%s