diff --git a/lib/Lacuna/DB/Result/Ships.pm b/lib/Lacuna/DB/Result/Ships.pm index 05d356c7..343d8dc9 100644 --- a/lib/Lacuna/DB/Result/Ships.pm +++ b/lib/Lacuna/DB/Result/Ships.pm @@ -25,8 +25,8 @@ __PACKAGE__->add_columns( shipyard_id => { data_type => 'int', is_nullable => 0 }, date_started => { data_type => 'datetime', is_nullable => 0, set_on_create => 1 }, date_available => { data_type => 'datetime', is_nullable => 0, set_on_create => 1 }, - type => { data_type => 'varchar', size => 30, is_nullable => 0 }, # probe, colony_ship, spy_pod, cargo_ship, space_station, smuggler_ship, mining_platform_ship, terraforming_platform_ship, gas_giant_settlement_ship - task => { data_type => 'varchar', size => 30, is_nullable => 0 }, # Docked, Building, Travelling, Mining, Defend, Orbiting + type => { data_type => 'varchar', size => 30, is_nullable => 0 }, # Look at typecast_map below + task => { data_type => 'varchar', size => 30, is_nullable => 0 }, # Building, Defend, Docked, Mining, Orbiting, Supply Chain, Travelling, Waiting on Trade, and Waste Chain name => { data_type => 'varchar', size => 30, is_nullable => 0 }, speed => { data_type => 'int', is_nullable => 0 }, stealth => { data_type => 'int', is_nullable => 0 }, @@ -39,8 +39,10 @@ __PACKAGE__->add_columns( foreign_star_id => { data_type => 'int', is_nullable => 1 }, fleet_speed => { data_type => 'int', is_nullable => 0 }, berth_level => { data_type => 'int', is_nullable => 0 }, + number_of_docks => { data_type => 'int', is_nullable => 1 }, ); __PACKAGE__->typecast_map(type => { + 'attack_group' => 'Lacuna::DB::Result::Ships::AttackGroup', #attack ships combined into one group. 'probe' => 'Lacuna::DB::Result::Ships::Probe', 'stake' => 'Lacuna::DB::Result::Ships::Stake', 'supply_pod' => 'Lacuna::DB::Result::Ships::SupplyPod', @@ -294,6 +296,7 @@ sub get_status { payload => $self->format_description_of_payload, can_scuttle => ($self->task eq 'Docked') ? 1 : 0, can_recall => ($self->task ~~ [qw(Defend Orbiting)]) ? 1 : 0, + number_of_docks => $self->number_of_docks, ); if ($target) { $status{estimated_travel_time} = $self->calculate_travel_time($target); @@ -375,7 +378,6 @@ sub send { $self->direction($options{direction} || 'out'); my $arrival = $options{arrival} || DateTime->now->add(seconds=>$self->calculate_travel_time($options{target})); $self->date_available($arrival); - if ($options{target}->isa('Lacuna::DB::Result::Map::Body')) { $self->foreign_body_id($options{target}->id); $self->foreign_body($options{target}); @@ -394,6 +396,124 @@ sub send { } $self->update; + my @ag_list = ("attack_group","sweeper","snark","snark2","snark3", + "observator_seeker","spaceport_seeker","security_ministry_seek", + "scanner","surveyor","detonator","bleeder","thud", + "scow","scow_large","scow_mega","scow_fast"); + + my $now = DateTime->now; + my $time2arrive = $now->subtract_datetime_absolute($self->date_available); + my $seconds = $time2arrive->seconds; + if ($seconds > 1800 && grep { $self->type eq $_ } @ag_list) { #Don't try to consolidate within 1/2 hour + my $dtf = Lacuna->db->storage->datetime_parser; + my $ships_rs = Lacuna->db->resultset('Ships')->search({ + body_id => $self->body_id, + foreign_body_id => $self->foreign_body_id, + task => 'Travelling', + type => { 'in' => \@ag_list }, + date_available => { '<' => $dtf->format_datetime($now->subtract(seconds => ($seconds + 900))) }, + date_available => { '>' => $dtf->format_datetime($now->subtract(seconds => ($seconds - 900))) }, + }); + my $payload = {}; + if ($self->type eq "attack_group") { #Turn ship into attack group if not already one + $payload = $self->payload; + } + else { + my $key = sprintf("%s:%05d:%05d:%05d:%09d", + $self->type, + $self->speed, + $self->combat, + $self->stealth, + $self->hold_size); + $payload->{fleet}->{$key} = { + type => $self->type, + speed => $self->speed, + combat => $self->combat, + stealth => $self->stealth, + hold_size => $self->hold_size, + target_building => $self->target_building, + quantity => 1, + }; + $self->type("attack_group"); + $self->update; + } + my $date_available = $self->date_available; + my $attack_group = { + speed => $self->speed, + stealth => $self->stealth, + hold_size => $self->hold_size, + combat => $self->combat, + target_building => $self->target_building, + number_of_docks => $self->number_of_docks, + }; + while (my $ship = $ships_rs->next) { + next if ($ship->id == $self->id); + if ($ship->speed < $attack_group->{speed}) { + $attack_group->{speed} = $ship->speed; + } + if ($ship->speed < $attack_group->{stealth}) { + $attack_group->{stealth} = $ship->stealth; + } + $attack_group->{combat} += $ship->combat; + $attack_group->{hold_size} += $ship->hold_size; #This really is only good for scows + $attack_group->{number_of_docks} += $ship->number_of_docks; + if ($ship->type eq "attack_group") { + if ($ship->payload->{resources}->{waste}) { + if ($payload->{resources}->{waste}) { + $payload->{resources}->{waste} += $ship->payload->{resources}->{waste}; + } + else { + $payload->{resources}->{waste} = $ship->payload->{resources}->{waste}; + } + } + for my $key (keys %{$ship->payload->{fleet}}) { + if ($payload->{fleet}->{$key}) { + $payload->{fleet}->{$key}->{quantity} += $ship->payload->{fleet}->{$key}->{quantity}; + } + else { + %{$payload->{fleet}->{$key}} = %{$ship->payload->{fleet}->{$key}}; + } + } + } + else { + my $key = sprintf("%s:%05d:%05d:%05d:%09d", + $ship->type, + $ship->speed, + $ship->combat, + $ship->stealth, + $ship->hold_size); + if ($payload->{fleet}->{$key}) { + $payload->{fleet}->{$key}->{quantity}++; + } + else { + $payload->{fleet}->{$key} = { + type => $ship->type, + speed => $ship->speed, + combat => $ship->combat, + stealth => $ship->stealth, + hold_size => $ship->hold_size, + target_building => $self->target_building, + quantity => 1, + }; + } + } + if ($ship->date_available > $date_available) { + $date_available = $ship->date_available; + } + $ship->delete; + } + $self->name("Attack Group"); + $self->speed($attack_group->{speed}); + $self->combat($attack_group->{combat}); + $self->stealth($attack_group->{stealth}); + $self->payload($payload); + $self->hold_size($attack_group->{hold_size}); + $self->number_of_docks($attack_group->{number_of_docks}); + $self->berth_level(1); + $self->date_available($date_available); + $self->update; + } + my $schedule = Lacuna->db->resultset('Schedule')->create({ delivery => $arrival, queue => 'arrive_queue', diff --git a/lib/Lacuna/DB/Result/Ships/AttackGroup.pm b/lib/Lacuna/DB/Result/Ships/AttackGroup.pm new file mode 100644 index 00000000..b8d2ddb7 --- /dev/null +++ b/lib/Lacuna/DB/Result/Ships/AttackGroup.pm @@ -0,0 +1,31 @@ +package Lacuna::DB::Result::Ships::AttackGroup; + +use Moose; +use utf8; +no warnings qw(uninitialized); +extends 'Lacuna::DB::Result::Ships'; + +use constant prereq => [ { class=> 'Lacuna::DB::Result::Building::University', level => 35 } ]; +use constant base_food_cost => 1_000_000_000_000; +use constant base_water_cost => 1_000_000_000_000; +use constant base_energy_cost => 1_000_000_000_000; +use constant base_ore_cost => 1_000_000_000_000; +use constant base_time_cost => 120 * 24 * 60 * 60; +use constant base_waste_cost => 39600; +use constant base_speed => 0; +use constant base_stealth => 0; +use constant base_combat => 0; +use constant base_hold_size => 0; +use constant build_tags => ['Kludge']; + +with "Lacuna::Role::Ship::Arrive::TriggerDefense"; +with "Lacuna::Role::Ship::Arrive::DamageBuilding"; +# with "Lacuna::Role::Ship::Arrive::DumpWaste"; #Annoyingly clobbers payload +with "Lacuna::Role::Ship::Arrive::DeployBleeder"; +with "Lacuna::Role::Ship::Arrive::DeploySmolderingCrater"; +with "Lacuna::Role::Ship::Arrive::ScanSurface"; +with "Lacuna::Role::Ship::Arrive::SurveySurface"; +with "Lacuna::Role::Ship::Arrive::GroupHome"; + +no Moose; +__PACKAGE__->meta->make_immutable(inline_constructor => 0); diff --git a/lib/Lacuna/DB/Result/Ships/Sweeper.pm b/lib/Lacuna/DB/Result/Ships/Sweeper.pm index 4b9e0546..a2276850 100644 --- a/lib/Lacuna/DB/Result/Ships/Sweeper.pm +++ b/lib/Lacuna/DB/Result/Ships/Sweeper.pm @@ -12,7 +12,7 @@ use constant base_energy_cost => 70000; use constant base_ore_cost => 75000; use constant base_time_cost => 60 * 60 * 7; use constant base_waste_cost => 20000; -use constant base_combat => 8100; +use constant base_combat => 6200; use constant base_speed => 2600; use constant base_stealth => 2800; use constant pilotable => 1; diff --git a/lib/Lacuna/RPC/Building/SpacePort.pm b/lib/Lacuna/RPC/Building/SpacePort.pm index 310b75d7..65e5e5bf 100644 --- a/lib/Lacuna/RPC/Building/SpacePort.pm +++ b/lib/Lacuna/RPC/Building/SpacePort.pm @@ -280,9 +280,9 @@ sub send_ship_types { # calculate the total ships before the expense of any database operations. my $total_ships = 0; map {$total_ships += $_->{quantity}} @$type_params; - my $max_ships = Lacuna->config->get('ships_per_fleet') || 20; + my $max_ships = Lacuna->config->get('ships_per_fleet') || 500; if ($total_ships > $max_ships) { - confess [1009, 'Too many ships for a fleet.']; + confess [1009, sprintf("Too many ships for a fleet, number must be less than or equal to %d.", $max_ships)]; } my $ship_ref; @@ -296,26 +296,29 @@ sub send_ship_types { my $quantity = $type_param->{quantity}; confess [1009, "Cannot send more than one excavator"] if ($type eq 'excavator' and $quantity > 1); - # TODO Must check for valid berth levels - # + my $max_berth = $body->max_berth; + unless ($max_berth) { + $max_berth = 1; + } my $ships_rs = Lacuna->db->resultset('Ships')->search({ body_id => $body->id, task => 'Docked', type => $type, + berth_level => {'<=' => $max_berth }, }); # handle optional parameters $ships_rs = $ships_rs->search({ speed => {'>=' => $type_param->{speed}}}) if defined $type_param->{speed}; $ships_rs = $ships_rs->search({ stealth => {'>=' => $type_param->{stealth}}}) if defined $type_param->{stealth}; $ships_rs = $ships_rs->search({ combat => {'>=' => $type_param->{combat}}}) if defined $type_param->{combat}; - if ($ships_rs->count < $quantity) { confess [1009, "Cannot find $quantity of $type ships."]; } - my @ships = $ships_rs->search(undef,{rows => $quantity}); - my $ship = $ships[0]; + my @ships = $ships_rs->search(undef,{rows => $quantity}); #Perhaps sort by speed? + my $ship = $ships[0]; #Need to grab slowest ship # We only need to check one of the ships $ship->can_send_to_target($target); #Check speed of ship. If it can not make it to the target in time, fail +#If time to target is longer than 60 days, fail. my $earliest = DateTime->now->add(seconds=>$ship->calculate_travel_time($target)); if ($earliest > $arrival) { confess [1009, "Cannot set a speed earlier than possible arrival time."]; @@ -331,10 +334,87 @@ sub send_ship_types { $empire->current_session->check_captcha; } # If we get here without exceptions, then all ships can be sent + +# Create attack_group + my @ag_list = ("sweeper","snark","snark2","snark3", + "observator_seeker","spaceport_seeker","security_ministry_seek", + "scanner","surveyor","detonator","bleeder","thud", + "scow","scow_large","scow_fast","scow_mega"); + my $cnt = 0; + my %ag_hash = map { $_ => $cnt++ } @ag_list; + my $attack_group = { + speed => 50_000, + stealth => 50_000, + hold_size => 0, + combat => 0, + number_of_docks => 0, + }; + my %payload; + $payload{fleet} = {}; + foreach my $ship (values %$ship_ref) { - $body->add_to_neutral_entry($ship->combat); - $ship->fleet_speed(1); - $ship->send(target => $target, arrival => $arrival); + if (grep { $ship->type eq $_ } @ag_list) { + my $sort_val = $ag_hash{$ship->type}; + if ($ship->speed < $attack_group->{speed}) { + $attack_group->{speed} = $ship->speed; + } + if ($ship->speed < $attack_group->{stealth}) { + $attack_group->{stealth} = $ship->stealth; + } + $attack_group->{combat} += $ship->combat; + $attack_group->{hold_size} += $ship->hold_size; #This really is only good for scows + $attack_group->{number_of_docks}++; + my $key = sprintf("%02d:%s:%05d:%05d:%05d:%09d", + $sort_val, + $ship->type, + $ship->combat, + $ship->speed, + $ship->stealth, + $ship->hold_size); + if ($payload{fleet}->{$key}) { + $payload{fleet}->{$key}->{quantity}++; + } + else { + $payload{fleet}->{$key} = { + type => $ship->type, + name => $ship->name, + speed => $ship->speed, + combat => $ship->combat, + stealth => $ship->stealth, + hold_size => $ship->hold_size, + target_building => $ship->target_building, + damage_taken => 0, + quantity => 1, + }; + } + $ship->delete; + } + else { + $ship->fleet_speed(1); + $ship->send(target => $target, arrival => $arrival); + $body->add_to_neutral_entry($ship->combat); + } + } + if ($attack_group->{number_of_docks} > 0) { + my $ag = $body->ships->new({ + type => "attack_group", + name => "Attack Group", + speed => $attack_group->{speed}, + combat => $attack_group->{combat}, + stealth => $attack_group->{stealth}, + payload => \%payload, + hold_size => $attack_group->{hold_size}, + fleet_speed => 1, + berth_level => 1, + body_id => $body->id, + task => 'Docked', + number_of_docks => $attack_group->{number_of_docks}, + })->insert; + $ag->send(target => $target, arrival => $arrival); + $body->add_to_neutral_entry($attack_group->combat); + } + else { + $attack_group->delete; } return $self->get_fleet_for($session_id, $body_id, $target_params); } @@ -374,9 +454,14 @@ sub send_fleet { unless ($set_speed >= 0) { confess [1009, 'Set speed cannot be less than zero.']; } +#If time to target is longer than 60 days, fail. $speed = $set_speed if ($set_speed > 0 && $set_speed < $speed); my @ret; my $captcha_check = 1; + +# Check captcha if needed +# Create attack_group + for my $ship_id (@fleet) { my $ship = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->find($ship_id); my $body = $ship->body; diff --git a/lib/Lacuna/Role/Ship/Arrive/DamageBuilding.pm b/lib/Lacuna/Role/Ship/Arrive/DamageBuilding.pm index c1b3f425..0e0314c1 100644 --- a/lib/Lacuna/Role/Ship/Arrive/DamageBuilding.pm +++ b/lib/Lacuna/Role/Ship/Arrive/DamageBuilding.pm @@ -11,54 +11,84 @@ after handle_arrival_procedures => sub { # we're coming home return if ($self->direction eq 'in'); - # determine damage - my $amount = randint(10,70); - - # determine target building - my $building; - my $body_attacked = $self->foreign_body; - my ($citadel) = grep { - $_->class eq 'Lacuna::DB::Result::Building::Permanent::CitadelOfKnope' - and $_->effective_efficiency == 100 - and $_->effective_level > 0 - and $_->is_upgrading == 0 - } @{$body_attacked->building_cache}; - - if (defined $citadel) { - if ($citadel->is_working) { - # Can we add one more ship? - my $now = DateTime->now; - my $cooldown = $citadel->work_ends->epoch - $now->epoch; - my $add_secs = 900 / $citadel->effective_level; - if ($cooldown + $add_secs > 3600) { - # we can't add any more time - undef $citadel; +#Check if attack group +# If groups of snarks +# check if reasonable to assume zero out everything on planet (depends on type, etc...) +# if not, go thru an attack for each. +# else return and go to next step +# 1) Group of snarks? +# 2) How many target buildings? +# 3) See if reasonable that buildings get zeroed? + my %snarks = { + snarks => { + count => 0, + target => [], + }, + observatory_seeker => { + count => 0, + target => [], + }, + spaceport_seeker => { + count => 0, + target => [], + }, + security_ministry_seeker => { + count => 0, + target => [], + }, + }; + if ($self->type eq "attack_group") { + my $payload = $self->payload; + for my $fleet (keys %{$payload->{fleet}}) { + if ($payload->{fleet}->{$fleet}->{type} eq "snark") { + $snarks{snarks}->{count} += $payload->{fleet}->{$fleet}->{quantity}; } - } - } - - if (defined $citadel) { - $building = $citadel; - } - elsif (scalar @{$self->target_building} > 0) { - my @builds; - for my $tb ( @{$self->target_building} ) { - my @temp = $body_attacked->get_buildings_of_class($tb); - if (@temp) { - push @builds, @temp; + elsif ($payload->{fleet}->{$fleet}->{type} eq "snark2") { + $snarks{snarks}->{count} += 4 * $payload->{fleet}->{$fleet}->{quantity}; } - } - ($building) = - sort { - $b->efficiency <=> $a->efficiency || - rand() <=> rand() + elsif ($payload->{fleet}->{$fleet}->{type} eq "snark3") { + $snarks{snarks}->{count} += 9 * $payload->{fleet}->{$fleet}->{quantity}; } - grep { - ($_->efficiency > 0) - } @builds; + elsif ($payload->{fleet}->{$fleet}->{type} eq "observatory_seeker") { + $snarks{observatory_seeker}->{count} += $payload->{fleet}->{$fleet}->{quantity}; + $snarks{observatory_seeker}->{target} = $payload->{fleet}->{$fleet}->{target_building}; + } + elsif ($payload->{fleet}->{$fleet}->{type} eq "security_ministry_seeker") { + $snarks{security_ministry_seeker}->{count} += $payload->{fleet}->{$fleet}->{quantity}; + $snarks{security_ministry_seeker}->{target} = $payload->{fleet}->{$fleet}->{target_building}; + } + elsif ($payload->{fleet}->{$fleet}->{type} eq "spaceport_seeker") { + $snarks{spaceport_seeker}->{count} += $payload->{fleet}->{$fleet}->{quantity}; + $snarks{spaceport_seeker}->{target} = $payload->{fleet}->{$fleet}->{target_building}; + } + } } else { - ($building) = + my $type = $self->type; + if ($type eq "snark") { + $snarks{snarks}->{count} = 1; + } + elsif ($type eq "snark2") { + $snarks{snarks}->{count} = 4; + } + elsif ($type eq "snark3") { + $snarks{snarks}->{count} = 9; + } + elsif ($type eq "observatory_seeker") { + $snarks{observatory_seeker}->{count} = 1; + $snarks{observatory_seeker}->{target} = $self->target_building; + } + elsif ($type eq "security_ministry_seeker") { + $snarks{security_ministry_seeker}->{count} = 1; + $snarks{security_ministry_seeker}->{target} = $self->target_building; + } + elsif ($type eq "spaceport_seeker") { + $snarks{spaceport_seeker}->{count} = 1; + $snarks{spaceport_seeker}->{target} = $self->target_building; + } + } + my $body_attacked = $self->foreign_body; + my @all_builds = sort { $b->efficiency <=> $a->efficiency || rand() <=> rand() @@ -70,45 +100,78 @@ after handle_arrival_procedures => sub { ($_->class ne 'Lacuna::DB::Result::Building::TheDillonForge') and ($_->class ne 'Lacuna::DB::Result::Building::Permanent::CitadelOfKnope') } @{$body_attacked->building_cache}; + + if ( $snarks{snarks}->{count}/4 > scalar @all_builds) { +#More than capable of zeroing all buildings + for my $building (@all_builds) { + $building->spend_efficiency(100)->update; + } +#Deal with email and log results } - return if not defined $building; + else { + my $building; + for my $sn_type ("observatory_seeker", "security_ministry_seeker", + "spaceport_seeker", "snark_count") { + my @tbuilds; + if (scalar @{$snarks{$sn_type}->{target}} > 0) { + for my $tb ( @{$snarks{$sn_type}->{target}}) { + my @temp = $body_attacked->get_buildings_of_class($tb); + if (@temp) { + push @tbuilds, @temp; + } + } + } + else { + @tbuilds = @all_builds; + } + if ($snarks{$sn_type}->{count}/4 > scalar @tbuilds) { + for $building (@tbuilds) { + $building->spend_efficiency(100)->update; +#Deal with email and log results + } + } + else { + my $count = 0; + for (0..$snarks{$sn_type}->{count}) { + my $amount = randint(10,70); + ($building) = + sort { + $b->efficiency <=> $a->efficiency || + rand() <=> rand() + } + grep { + ($_->efficiency > 0) + } @tbuilds; + if ($building) { + $building->spend_efficiency($amount)->update; +#Deal with email and log results + } + } + } + } + } + +#Send email, n19 and battle log # let everyone know what's going on + my $building_name = "Trash"; unless ($body_attacked->empire->skip_attack_messages) { - if (defined $citadel) { - $body_attacked->empire->send_predefined_message( - tags => ['Attack','Alert'], - filename => 'citadel_repelled_ship.txt', - params => [$self->type_formatted, $body_attacked->id, $body_attacked->name], - ); - } - else { - $body_attacked->empire->send_predefined_message( - tags => ['Attack','Alert'], - filename => 'ship_hit_building.txt', - params => [$self->type_formatted, $building->name, $body_attacked->id, - $body_attacked->name, $self->body->empire_id, $self->body->empire->name], + $body_attacked->empire->send_predefined_message( + tags => ['Attack','Alert'], + filename => 'ship_hit_building.txt', + params => [$self->type_formatted, $building_name, $body_attacked->id, + $body_attacked->name, $self->body->empire_id, $self->body->empire->name], ); - } } unless ($self->body->empire->skip_attack_messages) { - if (defined $citadel) { - $self->body->empire->send_predefined_message( - tags => ['Attack','Alert'], - filename => 'ship_repelled_by_citadel.txt', - params => [$self->type_formatted, $body_attacked->x, $body_attacked->y, $body_attacked->name], - ); - } - else { - $self->body->empire->send_predefined_message( - tags => ['Attack','Alert'], - filename => 'our_ship_hit_building.txt', - params => [$self->type_formatted, $body_attacked->x, $body_attacked->y, - $body_attacked->name, $building->name, $amount], + $self->body->empire->send_predefined_message( + tags => ['Attack','Alert'], + filename => 'our_ship_hit_building.txt', + params => [$self->type_formatted, $body_attacked->x, $body_attacked->y, + $body_attacked->name, $building_name, 100], ); - } } - $body_attacked->add_news(70, sprintf('An attack ship screamed out of the sky and damaged the %s on %s.',$building->name, $body_attacked->name)); + $body_attacked->add_news(70, sprintf('An attack ship screamed out of the sky and damaged the %s on %s.',$building_name, $body_attacked->name)); my $log = Lacuna->db->resultset('Log::Battles')->new({ date_stamp => DateTime->now, @@ -122,8 +185,8 @@ after handle_arrival_procedures => sub { defending_empire_name => $body_attacked->empire->name, defending_body_id => $body_attacked->id, defending_body_name => $body_attacked->name, - defending_unit_name => sprintf("%s (%d,%d)", $building->name, $building->x, $building->y), - defending_type => $building->name, + defending_unit_name => sprintf("%s (%d,%d)", $building_name, 0, 0), + defending_type => $building_name, attacked_empire_id => $body_attacked->empire_id, attacked_empire_name => $body_attacked->empire->name, attacked_body_id => $body_attacked->id, @@ -131,53 +194,11 @@ after handle_arrival_procedures => sub { victory_to => 'attacker', }); - if (defined $citadel) { - # handle citadel defence - # - $log->victory_to('defender'); - - my $add_secs = 900 / $citadel->effective_level; - if ($citadel->is_working) { - $citadel->reschedule_work($citadel->work_ends->add( seconds => $add_secs)); - } - else { - $citadel->start_work({}, $add_secs); - } - - # Repel the ship at quarter speed - $self->turn_around(int($self->speed / 4)); - $self->update; - $log->insert; - confess [-1]; - } - else { - # Handle regular building damage - # - $building->spend_efficiency($amount)->update; - if ($self->splash_radius) { - foreach my $i (1..$self->splash_radius) { - $amount /= $i + 1; - my @splashed = - grep { - ($_->x >= $building->x - $i) and - ($_->x <= $building->x + $i) and - ($_->y >= $building->y - $i) and - ($_->y <= $building->y + $i) and - ($_->class ne 'Lacuna::DB::Result::Building::Permanent::Crater') and - ($_->class ne 'Lacuna::DB::Result::Building::DeployedBleeder') and - ($_->class ne 'Lacuna::DB::Result::Building::TheDillonForge') # and -# ($_->class ne 'Lacuna::DB::Result::Building::Permanent::CitadelOfKnope') - } @{$body_attacked->building_cache}; - foreach my $damaged (@splashed) { - $damaged->body($body_attacked); - $damaged->spend_efficiency($amount)->update; - } - } - } + $log->insert; + if ($self->type ne "attack_group") { $self->delete; + confess [-1]; } - $log->insert; - confess [-1]; }; 1; diff --git a/lib/Lacuna/Role/Ship/Arrive/DumpWaste.pm b/lib/Lacuna/Role/Ship/Arrive/DumpWaste.pm index bccfbfcc..1bb42b53 100644 --- a/lib/Lacuna/Role/Ship/Arrive/DumpWaste.pm +++ b/lib/Lacuna/Role/Ship/Arrive/DumpWaste.pm @@ -80,19 +80,38 @@ after handle_arrival_procedures => sub { after send => sub { my ($self, %options ) = @_; - if (!$options{emptyscow}) - { - my $waste_sent; + my $waste_sent; + + if ($self->type eq "attack_group") { + my $payload = $self->payload; + my $hold_size = $self->hold_size; + my $room; + if ($payload->{resources}->{waste}) { + $room = $hold_size - $payload->{resources}->{waste}; + } + else { + $room = $hold_size; + } + if ($self->body->waste_stored < $room) { + $waste_sent = $self->body->waste_stored > 0 ? $self->body->waste_stored : 0; + } + else { + $waste_sent = $room; + } + $payload->{resources}->{waste} = $waste_sent; + $self->payload($payload); + } + else { if ($self->body->waste_stored < $self->hold_size) { - $waste_sent = $self->body->waste_stored > 0 ? $self->body->waste_stored : 0; + $waste_sent = $self->body->waste_stored > 0 ? $self->body->waste_stored : 0; } else { - $waste_sent = $self->hold_size; + $waste_sent = $self->hold_size; } - $self->body->spend_waste($waste_sent)->update; $self->payload({ resources => { waste => $waste_sent } }); - $self->update; } + $self->body->spend_waste($waste_sent)->update; + $self->update; }; after can_send_to_target => sub { diff --git a/lib/Lacuna/Role/Ship/Arrive/GroupHome.pm b/lib/Lacuna/Role/Ship/Arrive/GroupHome.pm new file mode 100644 index 00000000..7fd5071b --- /dev/null +++ b/lib/Lacuna/Role/Ship/Arrive/GroupHome.pm @@ -0,0 +1,67 @@ +package Lacuna::Role::Ship::Arrive::GroupHome; + +use strict; +use Moose::Role; +use Lacuna::Util qw(randint); +use DateTime; + +after handle_arrival_procedures => sub { + my ($self) = @_; + + # we're coming home + my $payload = $self->payload; + if ($self->direction eq 'in') { + for my $part (@{$payload->{fleet}}) { + if ($part->{type} eq "sweeper") { + for my $num (1..$part->{quantity}) { + my $ship = Lacuna->db->resultset('Ships')->new({type => "sweeper"}); + $ship->name($part->{name}); + $ship->body_id($self->body_id); + $ship->speed($part->{speed}); + $ship->combat($part->{combat}); + $ship->stealth($part->{stealth}); + $ship->hold_size(0); + $ship->date_available(DateTime->now); + $ship->date_started(DateTime->now); + $ship->insert; + } + } + } + $self->delete; + confess[-1]; + } + +# If not, strip all but sweepers and we'll turn around + my @new_fleet; + my $new_combat = 0; + my $new_quantity = 0; + my $new_stealth = 50_000; + my $new_speed = 50_000; + for my $part (@{$payload->{fleet}}) { + if ($part->{type} eq "sweeper") { + $new_quantity += $part->{quantity}; + $new_combat += $part->{combat}; + $new_stealth = $part->{stealth} if ($part->{stealth} < $new_stealth); + $new_speed = $part->{speed} if ($part->{speed} < $new_speed); + push @new_fleet, $part; + } + } + if ($new_quantity > 0 and $new_combat > 0) { + $payload = { + fleet => \@new_fleet, + quantity => $new_quantity, + damage_taken => 0, + }; + $self->combat($new_combat); + $self->stealth($new_stealth); + $self->speed($new_speed); + $self->fleet_speed($new_speed); + } + else { + $self->delete; + confess[-1]; + } + return; +}; + +1; diff --git a/lib/Lacuna/Role/Ship/Arrive/TriggerDefense.pm b/lib/Lacuna/Role/Ship/Arrive/TriggerDefense.pm index 23004b09..b1d20c1b 100644 --- a/lib/Lacuna/Role/Ship/Arrive/TriggerDefense.pm +++ b/lib/Lacuna/Role/Ship/Arrive/TriggerDefense.pm @@ -11,6 +11,9 @@ after handle_arrival_procedures => sub { # no defense at stars return unless $self->foreign_body_id; + # only trigger defenses on arrival to the foreign body + return if $self->direction eq 'in'; + # No defense in Neutral Area. (Can't stop colonization, mining, etc...) return if $self->foreign_body->in_neutral_area; @@ -25,8 +28,8 @@ after handle_arrival_procedures => sub { # no defense against self return if ( $is_planet && $body_attacked->empire && $body_attacked->empire_id == $ship_body->empire_id ); - # only trigger defenses on arrival to the foreign body - return if $self->direction eq 'in'; + # no defense against allies + return if ( $is_planet && $body_attacked->empire && $body_attacked->empire->alliance_id == $ship_body->empire->alliance_id ); # set last attack status $body_attacked->set_last_attacked_by($ship_body->id); @@ -34,139 +37,111 @@ after handle_arrival_procedures => sub { # subtract from time being able to jump to neutral area $body_attacked->subtract_from_neutral_entry(int($self->combat/5)); - # get SAWs - $self->system_saw_combat; + # Do Saw combat + $self->system_saw_combat(); - # get allies + # Do all fighters orbiting $self->allied_combat(); + # Do all ships that are defending from dock (drones, fighters, sweepers) $self->defender_combat(); -}; - -sub damage_in_combat { - my ($self, $defender, $damage) = @_; - $self->combat( $self->combat - $damage ); - return unless $self->combat < 1; - $self->attacker_shot_down($defender); - $self->delete; - confess [-1] -} -sub attacker_shot_down { - my ($self, $defender) = @_; - my $body_attacked = $self->foreign_body; +}; - unless ($self->body->empire->skip_attack_messages) { - $self->body->empire->send_predefined_message( - tags => ['Attack','Alert'], - filename => 'ship_shot_down.txt', - params => [$self->type_formatted, - $body_attacked->x, - $body_attacked->y, - $body_attacked->name, - $self->body->id, - $self->body->name], - ); +sub citadel_interaction { + my ($body_cit, $combat_def, $combat_att, $no_add) = @_; + + my ($citadel) = grep { + $_->class eq 'Lacuna::DB::Result::Building::Permanent::CitadelOfKnope' + and $_->efficiency > 0 + and $_->level > 0 + } @{$body_cit->building_cache}; + + return $combat_def unless defined ($citadel); + + my $min_add = 900 / $citadel->effective_level; + my $cooldown = 0; + if ($citadel->is_working) { + # Can we add more time? + my $now = DateTime->now; + $cooldown += $citadel->work_ends->epoch - $now->epoch; + if ($cooldown + $min_add > 3600) { + # we can't add any more time + return $combat_def; + } } - - unless ($defender->body->empire_id && $defender->body->empire->skip_attack_messages) { - $defender->body->empire->send_predefined_message( - tags => ['Attack','Alert'], - filename => 'we_shot_down_a_ship.txt', - params => [$self->type_formatted, - $body_attacked->id, - $body_attacked->name, - $self->body->empire_id, - $self->body->empire->name], - ); + my $max_add = 3600 - $cooldown; + my $max_mult = 1 + ($max_add * $citadel->effective_efficiency * $citadel->effective_level)/3_600_000; + # Mult will between 1 and 4 + return ($max_mult * $combat_def) if $no_add; + + my $threshold = $combat_att/10; + my $add_secs = int($min_add * $threshold/$combat_def); + $add_secs = $max_add if ($add_secs > $max_add); + + if ($citadel->is_working) { + $citadel->reschedule_work($citadel->work_ends->add( seconds => $add_secs)); } + else { + $citadel->start_work({}, $add_secs); + } + return ($max_mult * $combat_def); - $defender->body->add_news(20, - sprintf('An amateur astronomer witnessed an explosion in the sky today over %s.', - $body_attacked->name)); - - my $is_asteroid = $body_attacked->isa('Lacuna::DB::Result::Map::Body::Asteroid'); - - log_attack( $self, $defender, 'defender' ); -# $self->log_attack( $defender, 'defender' ); -} - -sub saw_disabled { - my ($self, $defender) = @_; - my $body_attacked = $self->foreign_body; - - unless ($self->body->empire->skip_attack_messages) { - $self->body->empire->send_predefined_message( - tags => ['Attack','Alert'], - filename => 'saw_neutralized.txt', - params => [ - $defender->body->x, - $defender->body->y, - $defender->body->name, - $defender->body->empire_id, - $defender->body->empire->name, - $body_attacked->x, - $body_attacked->y, - $body_attacked->name], - ); - } - - unless ($defender->body->empire_id && $defender->body->empire->skip_attack_messages) { - $defender->body->empire->send_predefined_message( - tags => ['Attack','Alert'], - filename => 'saw_disabled.txt', - params => [$defender->body->x, - $defender->body->y, - $defender->body->name, - $body_attacked->x, - $body_attacked->y, - $body_attacked->name, - $self->body->empire_id, - $self->body->empire->name, - $self->type_formatted], - ); - } - log_attack($self, $defender, 'attacker'); } -sub defender_shot_down { - my ($self, $defender) = @_; - my $body_attacked = $self->foreign_body; - - unless ($self->body->empire->skip_attack_messages) { - $self->body->empire->send_predefined_message( - tags => ['Attack','Alert'], - filename => 'we_shot_down_a_defender.txt', - params => [$defender->type_formatted, - $body_attacked->x, - $body_attacked->y, - $body_attacked->name, - $defender->body->empire_id, - $defender->body->empire->name], - ); +sub damage_in_combat { + my ($self, $damage) = @_; + $self->combat( $self->combat - $damage ); + my $return; + my $abid = $self->body_id; + if ($self->type eq "attack_group") { + my $payload = $self->payload; + for my $key (sort keys %{$payload->{fleet}}) { + my ($sort_val, $type, $combat, $speed, $stealth, $hold_size) = split(/:/, $key); + if ($payload->{fleet}->{$key}->{combat} > $damage) { + $payload->{fleet}->{$key}->{combat} -= $damage; + my $ships_destroyed = int($damage/$combat); + $payload->{fleet}->{$key}->{quantity} -= $ships_destroyed; + $payload->{fleet}->{$key}->{number_of_docks} -= $ships_destroyed; + + if ($return->{$abid}->{$payload->{fleet}->{$key}->{type}}) { + $return->{$abid}->{$payload->{fleet}->{$key}->{type}}->{number} += $ships_destroyed; + } + else { + $return->{$abid}->{$payload->{fleet}->{$key}->{type}}->{body_id} = $abid; + $return->{$abid}->{$payload->{fleet}->{$key}->{type}}->{body_name} = $self->body->name; + $return->{$abid}->{$payload->{fleet}->{$key}->{type}}->{emp_id} = $self->body->empire_id; + $return->{$abid}->{$payload->{fleet}->{$key}->{type}}->{emp_name} = $self->body->empire->name; + $return->{$abid}->{$payload->{fleet}->{$key}->{type}}->{number} = $ships_destroyed; + } + $damage = 0; + } + else { + if ($return->{$abid}->{$payload->{fleet}->{$key}->{type}}) { + $return->{$abid}->{$payload->{fleet}->{$key}->{type}}->{number} += $payload->{fleet}->{$key}->{quantity}; + } + else { + $return->{$abid}->{$payload->{fleet}->{$key}->{type}}->{body_id} = $abid; + $return->{$abid}->{$payload->{fleet}->{$key}->{type}}->{body_name} = $self->body->name; + $return->{$abid}->{$payload->{fleet}->{$key}->{type}}->{emp_id} = $self->body->empire_id; + $return->{$abid}->{$payload->{fleet}->{$key}->{type}}->{emp_name} = $self->body->empire->name; + $return->{$abid}->{$payload->{fleet}->{$key}->{type}}->{number} += $payload->{fleet}->{$key}->{quantity}; + } + $damage -= $payload->{fleet}->{combat}; + delete $payload->{fleet}->{$key}; + } + last if $damage == 0; + } + if ($self->combat > 0) { +#No need to reset payload if ship is being destroyed. + $self->payload($payload); + } } - - unless ($defender->body->empire_id && $defender->body->empire->skip_attack_messages) { - $defender->body->empire->send_predefined_message( - tags => ['Attack','Alert'], - filename => 'defender_shot_down.txt', - params => [$defender->type_formatted, - $defender->body->id, - $defender->body->name, - $body_attacked->x, - $body_attacked->y, - $body_attacked->name], - ); + else { + $return->{$self->type} = 1 if $self->combat < 1; } - - $defender->body->add_news(20, - sprintf('An amateur astronomer witnessed an explosion in the sky today over %s.', - $body_attacked->name)); - - my $is_asteroid = $body_attacked->isa('Lacuna::DB::Result::Map::Body::Asteroid'); - - log_attack( $self, $defender, 'attacker' ); -# $self->log_attack( $defender, 'attacker' ); + $self->update; + return $return; } sub log_attack { @@ -207,35 +182,49 @@ sub log_attack { sub ship_to_ship_combat { my ($self, $ships) = @_; - my %body_empire; - my $empire = $self->body->empire_id; - my $alliance = $self->body->empire->alliance_id; +# $attack_stat = $self->damage_in_combat($total_combat); +# $self->notify_battle_results($defense_stat, $attack_stat); + my $attack_eid = $self->body->empire_id; + my $attack_aid = 0; + $attack_aid = $self->body->empire->alliance_id if ($self->body->alliance); + my $attack_combat = $self->combat; + + my $defense_stat; # if there are ships let's duke it out while (my $ship = $ships->next) { # don't fight our own ships - my $ship_empire = $body_empire{$ship->body_id} //= $ship->body->empire_id; - if ($empire == $ship_empire) { - next; - } + next if $attack_eid == $ship->body->empire_id; # don't fight our alliance - my $ship_alliance = $ship->body->empire->alliance_id; - if ( $alliance && $ship_alliance && $alliance == $ship_alliance ) { + if ( ($ship->body->empire->alliance && $attack_aid) && + ($ship->body->empire->alliance == $attack_aid)) { next; } # defender dealt this damage my $damage = $ship->combat; + $damage = citadel_interaction($self->foreign_body, $damage, $attack_combat, 1); + + my $dbid = $ship->body_id; + my $stype = $ship->type; + if ($defense_stat->{$dbid}->{$stype}) { + $defense_stat->{$dbid}->{$stype}->{number} += 1; + } + else { + $defense_stat->{$dbid}->{$stype}->{body_id} = $dbid; + $defense_stat->{$dbid}->{$stype}->{body_name} = $ship->body->name; + $defense_stat->{$dbid}->{$stype}->{emp_id} = $ship->body->empire_id; + $defense_stat->{$dbid}->{$stype}->{emp_name} = $ship->body->empire->name; + $defense_stat->{$dbid}->{$stype}->{number} = 1; + } if ($ship->type eq 'drone') { $ship->delete; } - else { # subtract attacker's damage dealt from defender - $ship->combat( $ship->combat - $self->combat ); + $ship->combat( $ship->combat - $attack_combat ); if ($ship->combat < 1) { - $self->defender_shot_down($ship); $ship->delete; } else { @@ -252,120 +241,228 @@ sub ship_to_ship_combat { } } } + $attack_combat -= $damage; + last if $attack_combat < 1; + } + my $attack_stat = $self->damage_in_combat($self->combat - $attack_combat); + $self->notify_battle_results($defense_stat, $attack_stat); + return unless $self->combat < 1; + $self->delete; + confess [-1] +} - $self->damage_in_combat($ship, $damage); +sub allied_combat { + my ($self) = @_; + + my $attacked_body = $self->foreign_body; + my $ship_body = $self->body; + my $is_planet = $attacked_body->isa('Lacuna::DB::Result::Map::Body::Planet'); + my $is_asteroid = $attacked_body->isa('Lacuna::DB::Result::Map::Body::Asteroid'); + my $is_station = 0; + $is_station = 1 if ($attacked_body->get_type eq 'space station'); + + my $defend_eid = 0; + my $defend_aid = 0; + my $attack_aid = 0; + if ($attacked_body->empire) { + $defend_eid = $attacked_body->empire_id; + $defend_aid = $attacked_body->empire->alliance_id if ($attacked_body->empire->alliance_id); } + + my $attack_eid = $ship_body->empire_id; + $attack_aid = $ship_body->empire->alliance_id if ($ship_body->alliance); + + if ($defend_aid != 0 and $attack_aid != 0) { + return if ($defend_aid == $attack_aid); + } + + if ($attacked_body->in_starter_zone and !$attacked_body->empire) { + return; + } + + my @allied_bodies; + if ($attack_aid) { + my @allied_empire = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->search( + { + alliance_id => $attack_aid, + }); + @allied_bodies = Lacuna->db->resultset('Lacuna::DB::Result::Body')->search( + { + empire_id => { 'in' => \@allied_empire, }, + }); + } + my $ship_db = Lacuna->db->resultset('Lacuna::DB::Result::Ships'); + my $fighters_orbit = $ship_db->search( + { + foreign_body_id => $attacked_body->id, + type => 'fighter', + task => 'Defend', + body_id => { 'not in' => \@allied_bodies, }, + }); + undef @allied_bodies; + # initiate ship to ship combat between the attackers and the allied ships + $self->ship_to_ship_combat($fighters_orbit); } sub defender_combat { - my ($self) = @_; + my ($self) = @_; # get defensive ships my $defense_ships = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->search( - { body_id => $self->foreign_body_id, type => { in => [ qw(fighter drone sweeper) ] }, task=>'Docked' }, - ); - - # initiate ship to ship combat between the attackers and the defensive ships - $self->ship_to_ship_combat($defense_ships); -} - -sub allied_combat { - my ($self) = @_; - my $body_attacked = $self->foreign_body; - my $is_planet = $body_attacked->isa('Lacuna::DB::Result::Map::Body::Planet'); - my $is_asteroid = $body_attacked->isa('Lacuna::DB::Result::Map::Body::Asteroid'); - - # get allied defenders - my $allied_ships = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->search( - { - foreign_body_id => $self->foreign_body_id, - type => 'fighter', - task => 'Defend', - }, - ); - - # initiate ship to ship combat between the attackers and the allied ships - $self->ship_to_ship_combat($allied_ships); + { + body_id => $self->foreign_body_id, + type => { in => [ qw(fighter drone sweeper) ] }, + task=>'Docked', + }); + + # initiate ship to ship combat between the attackers and the defensive ships + $self->ship_to_ship_combat($defense_ships); } sub system_saw_combat { - my ($self) = @_; - - my $attacked_body = $self->foreign_body; - my $ship_body = $self->body; - my $is_planet = $attacked_body->isa('Lacuna::DB::Result::Map::Body::Planet'); - my $is_asteroid = $attacked_body->isa('Lacuna::DB::Result::Map::Body::Asteroid'); + my ($self) = @_; - my $attacked_empire = $attacked_body->empire_id; - my $attacked_alliance = $attacked_body->alliance_id - || ($attacked_body->empire && $attacked_body->empire->alliance_id); + my $attacked_body = $self->foreign_body; + my $ship_body = $self->body; + my $is_planet = $attacked_body->isa('Lacuna::DB::Result::Map::Body::Planet'); + my $is_asteroid = $attacked_body->isa('Lacuna::DB::Result::Map::Body::Asteroid'); + my $is_station = 0; + $is_station = 1 if ($attacked_body->get_type eq 'space station'); + + my $defend_eid = 0; + my $defend_aid = 0; + my $attack_aid = 0; + if ($attacked_body->empire) { + $defend_eid = $attacked_body->empire_id; + $defend_aid = $attacked_body->empire->alliance_id if ($attacked_body->empire->alliance_id); + } - my $ship_empire = $ship_body->empire_id; - my $ship_alliance = $ship_body->empire->alliance_id; + my $ship_eid = $ship_body->empire_id; + my $ship_aid = $ship_body->empire->alliance_id; - if ($attacked_alliance && $ship_alliance) { - return if ($attacked_alliance == $ship_alliance); - } + if ($defend_aid != 0 and $ship_aid != 0) { + return if ($defend_aid == $ship_aid); + } - if ($attacked_body->in_starter_zone and !$attacked_body->empire) { - return; - } + if ($attacked_body->in_starter_zone and !$attacked_body->empire) { + return; + } - my $defending_bodies = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->search( - { id => { '!=' => $attacked_body->id}, star_id => $attacked_body->star_id } - ); + my $defending_bodies = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body') + ->search({ + 'me.star_id' => $attacked_body->star_id, + 'empire.alliance_id' => { '!=' => $ship_aid }, + }, + { + join => 'empire', + }); # Here's where we get total number of defending SAWs -# 1) Get Targetted Planet's SAWs first. -# 2) Go thru and get all SAWs hostile to incoming. - my $total_combat = 0; - my $saw_number = 0; - my @saws; - my $attacked_saws = []; - 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 - next - unless $defending_body->isa('Lacuna::DB::Result::Map::Body::Planet'); - - my $defending_empire = $defending_body->empire_id; - my $defending_alliance = $defending_body->alliance_id - || ($defending_body->empire && $defending_body->empire->alliance_id); - - if ( $defending_empire && $defending_empire == $ship_empire ) { - # don't attack ships from same empire +# 1) Get all Saws and total combat value + my $total_combat = 0; + my $saw_number = 0; + my @saws; + my $def_cnt = 0; + + my $defense_stat; + while (my $dbody = $defending_bodies->next) { + next unless $dbody->isa('Lacuna::DB::Result::Map::Body::Planet'); + my ($saws, $combat) = saw_stats($dbody); + next unless $combat > 0; + my $dbid = $dbody->id; + $def_cnt++; + $combat *= 1.55 if ($dbid == $attacked_body->id); + $combat *= 1.55 if ($dbody->empire_id == $attacked_body->empire_id); + $combat *= 1.55 if (($dbody->empire->alliance and $defend_aid != 0) and + ($dbody->empire->alliance_id == $defend_aid)); + $combat = citadel_interaction($dbody, $combat, $self->combat, 0); + push @saws, @$saws; + $total_combat += $combat; + $defense_stat->{$dbid}->{"Saws"}->{number} = scalar @$saws; + $defense_stat->{$dbid}->{"Saws"}->{body_id} = $dbid; + $defense_stat->{$dbid}->{"Saws"}->{body_name} = $dbody->name; + $defense_stat->{$dbid}->{"Saws"}->{emp_id} = $dbody->empire_id; + $defense_stat->{$dbid}->{"Saws"}->{emp_name} = $dbody->empire->name; } - elsif ( $defending_empire && $attacked_empire && $defending_empire == $attacked_empire ) { - # defend own planets in system - my ( $saws, $combat ) = $self->saw_stats($defending_body); - push @saws, @$saws; - $total_combat += $combat; + $total_combat *= $def_cnt if $is_station; + my $num_saws = scalar @saws; + my $percent = int($self->combat * 100/$total_combat); + my $max_eff = $percent + 1; + my $min_eff = int($max_eff/2); + for my $saw (@saws) { + my $effect = randint($min_eff,$max_eff); + $saw->spend_efficiency($effect); + unless ($saw->is_working) { + $saw->start_work({}, 60 * 15); + } + $saw->update; } - elsif ( $defending_alliance && $ship_alliance && $defending_alliance == $ship_alliance ) { - # don't attack ships in same alliance + my $attack_stat = $self->damage_in_combat($total_combat); + $self->notify_battle_results($defense_stat, $attack_stat); + return unless $self->combat < 1; + $self->delete; + confess [-1] +} + +sub notify_battle_results { + my ($self, $defense_stat, $attack_stat) = @_; + + my $report; + for my $dbid (keys %{$defense_stat}) { + for my $dtype (keys %{$defense_stat->{$dbid}}) { + push @{$report}, [ + $dbid, + $defense_stat->{$dbid}->{$dtype}->{body_name}, + $defense_stat->{$dbid}->{$dtype}->{emp_id}, + $defense_stat->{$dbid}->{$dtype}->{emp_name}, + $dtype, + $defense_stat->{$dbid}->{$dtype}->{number}, + ]; + } + } + unshift @{$report}, (['Defenders','','','','',''],['ID','Planet','EID','Empire','Type','Number']); + push @{$report}, (['Attackers','','','','',''],['ID','Planet','EID','Empire','Type','Number']); + for my $abid (keys %{$attack_stat}) { + for my $atype (keys %{$attack_stat->{$abid}}) { + push @{$report}, [ + $abid, + $attack_stat->{$abid}->{$atype}->{body_name}, + $attack_stat->{$abid}->{$atype}->{emp_id}, + $attack_stat->{$abid}->{$atype}->{emp_name}, + $atype, + $attack_stat->{$abid}->{$atype}->{number}, + ]; + } + } + + $self->body->empire->send_predefined_message( + tags => ['Attack'], + filename => 'attack_summary.txt', + params => [$self->foreign_body->id, + $self->foreign_body->name, + ], + attachments => { table => $report }, + ); + + if ($self->foreign_body->empire) { + $self->foreign_body->add_news(20, + sprintf('A space battle occured over %s today.', + $self->foreign_body->name)); } else { - # attack everything else - my ( $saws, $combat ) = $self->saw_stats($defending_body); - push @saws, @$saws; - $total_combat += $combat; + $self->body->add_news(20, + sprintf('Action reported by a %s squadron out of %s today.', + $self->body->empire->name, $self->body->name)); } - } -# Defending Planet SAWs go first, then random from other planets. - for my $saw ((shuffle @$attacked_saws), shuffle @saws) { - $self->saw_combat($saw, $total_combat); - } } sub saw_stats { - my ($self, $body) = @_; + my ($body) = @_; my @planet_saws = Lacuna->db->resultset('Building')->search({ body_id => $body->id, class => 'Lacuna::DB::Result::Building::SAW', + efficiency => { '>' => 0 }, + level => { '>' => 0 }, }); my $planet_combat = 0; @@ -373,44 +470,11 @@ sub saw_stats { my @defending_saws; for my $saw (@planet_saws) { $cnt++; - next if $saw->effective_level < 1; - next if $saw->effective_efficiency < 1; - $planet_combat += int( (5 * ($saw->effective_level + 1) * ($saw->effective_level+1) * $saw->effective_efficiency)/2 + 0.5); + $planet_combat += 60 * (($saw->effective_level + 1) * ($saw->effective_level + 1) * $saw->effective_efficiency); push @defending_saws, $saw; last if $cnt >= 10; } return \@defending_saws, $planet_combat; } -sub saw_combat { - my ($self, $saw, $total_combat) = @_; - - return if ($saw->efficiency == 0); -# printf "ship:%6d:%5d saw:%6d:%2d:%3d total:%8d ", -# $self->id, $self->combat, $saw->id, $saw->effective_level, $saw->effective_efficiency, $total_combat; - if ($self->combat >= $total_combat) { - $saw->spend_efficiency(100); - $self->saw_disabled($saw); -# print "100\n"; - } - else { - my $perc_1 = int( ($self->combat * 100)/$total_combat + 0.5); - my $perc_2 = int( $self->combat * 100/ - (5 * ($saw->effective_level + 1) * ($saw->effective_level+1) * $saw->effective_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_1); - } - unless ($saw->is_working) { - $saw->start_work({}, 60 * 15); - } - $saw->update; - $self->damage_in_combat($saw, $total_combat); -} - 1; diff --git a/var/messages/attack_summary.txt b/var/messages/attack_summary.txt new file mode 100644 index 00000000..2a8d32ac --- /dev/null +++ b/var/messages/attack_summary.txt @@ -0,0 +1,7 @@ +Attack Summary +~~~ +Planet {Planet %s %s} has been attacked. Attached is a summary. + +Regards, + +Your Humble Assistant diff --git a/var/messages/saw_engaged.txt b/var/messages/saw_engaged.txt new file mode 100644 index 00000000..b183e881 --- /dev/null +++ b/var/messages/saw_engaged.txt @@ -0,0 +1,7 @@ +Saws Engaged +~~~ +Our fleet have engaged the saws of the following empires while attacking {Planet %s %s}. + +Regards, + +Your Humble Assistant diff --git a/var/upgrades/fleet_lite.sql b/var/upgrades/fleet_lite.sql new file mode 100644 index 00000000..c6b1a4ea --- /dev/null +++ b/var/upgrades/fleet_lite.sql @@ -0,0 +1,2 @@ +alter table ships add column number_of_docks int default 1; +alter table ships modify hold_size bigint; diff --git a/var/www/public/changes.txt b/var/www/public/changes.txt index 81abd93a..45707b5c 100644 --- a/var/www/public/changes.txt +++ b/var/www/public/changes.txt @@ -1,6 +1,8 @@ 3.0909: - Add: Admin UI to set alliance logo. - Fix: Only one notification per empire per star on probe detection. + - Add: Fleet Lite + - Mod: Sweepers adjusted for balance. 3.0908: - Mod: trash_messages_where now defaults to not returning deleted message IDs.