File Coverage

blib/lib/Games/Lacuna/Task/Action/Defence.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package Games::Lacuna::Task::Action::Defence;
2              
3 1     1   1814 use 5.010;
  1         3  
  1         53  
4             our $VERSION = $Games::Lacuna::Task::VERSION;
5              
6 1     1   489 use Moose;
  0            
  0            
7             extends qw(Games::Lacuna::Task::Action);
8             with qw(Games::Lacuna::Task::Role::Ships
9             Games::Lacuna::Task::Role::PlanetRun);
10              
11             use List::Util qw(min);
12             use Games::Lacuna::Task::Utils qw(parse_date);
13              
14             has 'min_defender_combat' => (
15             isa => 'Int',
16             is => 'rw',
17             required => 1,
18             default => 10000,
19             documentation => 'Only defenders above or equal to this combat level will be considered [Default: 10000]',
20             );
21              
22             has '_planet_attack' => (
23             is => 'rw',
24             isa => 'HashRef',
25             default => sub { {} },
26             traits => ['Hash','NoGetopt'],
27             handles => {
28             _add_planet_attack => 'set',
29             _list_planet_attack => 'keys',
30             _has_planet_attack => 'defined',
31             }
32             );
33              
34             sub description {
35             return q[Defend against enemy attacks by dispatching fighters and sweepers];
36             }
37              
38             after 'run' => sub {
39             my ($self) = @_;
40            
41             my @dispatch_ships;
42             foreach my $body_id ($self->_list_planet_attack) {
43             my $planet_attack = $self->_list_planet_attack->{$body_id};
44            
45            
46             DISPATCH_PLANETS:
47             foreach my $dispatch_planet_stats ($self->my_planets) {
48             my $count = $planet_attack->{attacker} - $planet_attack->{defender};
49            
50             last DISPATCH_PLANETS
51             if $count <= 0;
52            
53             next DISPATCH_PLANETS
54             if $dispatch_planet_stats->{id} == $body_id;
55            
56             my $available = 100;
57             # Check if other planet is being attacked too
58             if ($self->_has_planet_attack($dispatch_planet_stats->{id})) {
59             my $dispatch_planet_attack = $self->_get_planet_attack($dispatch_planet_stats->{id});
60             next DISPATCH_PLANETS
61             if $dispatch_planet_attack->{attacker} > $dispatch_planet_attack->{defender};
62             $available = $dispatch_planet_attack->{defender} - $dispatch_planet_attack->{attacker};
63             }
64            
65             my $dispatch_count = $self->dispatch_defender($body_id,$dispatch_planet_stats->{id},min($count,$available));
66            
67             if ($dispatch_count) {
68             $planet_attack->{defender} += $dispatch_count;
69             $self->_list_planet_attack->{$body_id}{defender} -= $dispatch_count;
70            
71             my $body_status = $self->my_body_status($body_id);
72             push(@dispatch_ships,{
73             from_id => $dispatch_planet_stats->{id},
74             from_name => $dispatch_planet_stats->{name},
75             to_id => $body_id,
76             to_name => $body_status->{name},
77             count => $dispatch_count,
78             });
79             $self->log('info','Dispatching %i defending units from %s to %s',$dispatch_count,$dispatch_planet_stats->{name},$body_status->{name});
80             }
81             }
82             }
83            
84             if (scalar @dispatch_ships) {
85             my $body = sprintf("We have dispatched defending ships:\n%s",
86             join("\n", map {
87             sprintf("%i ships from {Planet %i %s} to {Planet %i %s}",
88             $_->{count},
89             $_->{from_id},
90             $_->{from_name},
91             $_->{to_id},
92             $_->{to_name},
93             );
94             } @dispatch_ships)
95             );
96             $self->send_message('Dispatched defenders',$body);
97             }
98             };
99              
100             sub process_planet {
101             my ($self,$planet_stats) = @_;
102            
103             # Check incoming ships
104             return
105             unless defined($planet_stats->{incoming_enemy_ships});
106            
107             # Get space port
108             my $spaceport = $self->find_building($planet_stats->{id},'SpacePort');
109            
110             return
111             unless $spaceport;
112            
113             my $spaceport_object = $self->build_object($spaceport);
114            
115             # Get all incoming ships
116             my $ships_data = $self->paged_request(
117             object => $spaceport_object,
118             method => 'view_foreign_ships',
119             total => 'number_of_ships',
120             data => 'ships',
121             );
122            
123             my $attacker_count = 0;
124             my $defender_count = 0;
125             my $first_attacker_arrive;
126            
127             my @possible_attacking_ships;
128             foreach my $ship (@{$ships_data->{ships}}) {
129             if (defined $ship->{from}
130             && defined $ship->{from}{empire}) {
131             # My own ship
132             next
133             if ($ship->{from}{empire}{id} == $planet_stats->{empire}{id});
134             }
135            
136             # Ignore cargo ships
137             next
138             if ($ship->{type} ~~ [qw(dory galleon hulk cargo_ship barge freighter smuggler_ship)]);
139            
140             my $arrives = parse_date($ship->{date_arrives});
141            
142             next
143             if (time() - $arrives) > (60 * 60 * 6); # six hours
144            
145             $first_attacker_arrive ||= $arrives;
146             $first_attacker_arrive = $arrives
147             if $arrives < $first_attacker_arrive;
148             $attacker_count++;
149             }
150            
151             return
152             if $attacker_count == 0;
153            
154             $self->log('info','%i attacking ships detected on %s',$attacker_count,$planet_stats->{name});
155            
156             # Count SAWs
157             $defender_count += $self->get_saws($planet_stats->{star_id});
158            
159             # Count local fighters & sweepers
160             $defender_count += $self->get_local_defending_ships($planet_stats->{id},$first_attacker_arrive);
161            
162             # Count orbiting fighters
163             $defender_count += $self->get_orbiting_defending_ships($planet_stats->{id},$first_attacker_arrive);
164            
165             # Recall foreign orbiting ships
166             if ($attacker_count > $defender_count) {
167             $defender_count += $self->recall_defender($planet_stats->{id},$first_attacker_arrive);
168             }
169            
170             # Store attacker & defender
171             $self->_add_planet_attack($planet_stats->{id},{
172             attacker => $attacker_count,
173             defender => $defender_count,
174             arrive => $first_attacker_arrive,
175             free_slots => $self->get_spaceport_slots($planet_stats->{id}),
176             });
177            
178             $self->log('info','%i defending units available on %s',$defender_count,$planet_stats->{name});
179             }
180              
181             sub get_orbiting_defending_ships {
182             my ($self,$body_id,$first_attacker_arrive) = @_;
183            
184             my $spaceport = $self->find_building($body_id,'SpacePort');
185             return 0
186             unless $spaceport;
187             my $spaceport_object = $self->build_object($spaceport);
188            
189             my $count = 0;
190            
191             # Get all available ships
192             my $ships_data = $self->paged_request(
193             object => $spaceport_object,
194             method => 'view_ships_orbiting',
195             total => 'number_of_ships',
196             data => 'ships',
197             );
198            
199             ORBITING_SHIPS:
200             foreach my $ship (@{$ships_data->{ships}}) {
201             next ORBITING_SHIPS
202             unless $ship->{type} eq 'fighter'
203             || $ship->{type} eq 'sweeper';
204            
205             # TODO check if orbiting ship is ally
206             next ORBITING_SHIPS
207             unless $ship->{from}{empire}{id} == $ships_data->{status}{empire}{id};
208            
209             $count++;
210             }
211            
212             return $count;
213             }
214              
215             sub get_spaceport_slots {
216             my ($self,$body_id) = @_;
217            
218             my $spaceport = $self->find_building($body_id,'SpacePort');
219            
220             return 0
221             unless scalar $spaceport;
222            
223             my $spaceport_object = $self->build_object($spaceport);
224            
225             # Get all available ships
226             my $spaceport_data = $self->request(
227             object => $spaceport_object,
228             method => 'view',
229             );
230            
231             return $spaceport_data->{docks_available};
232             }
233              
234             sub get_local_defending_ships {
235             my ($self,$body_id,$first_attacker_arrive) = @_;
236            
237             my $spaceport = $self->find_building($body_id,'SpacePort');
238             return 0
239             unless $spaceport;
240             my $spaceport_object = $self->build_object($spaceport);
241            
242             my $count = 0;
243            
244             # Get all available ships
245             my $ships_data = $self->request(
246             object => $spaceport_object,
247             method => 'view_all_ships',
248             params => [ { no_paging => 1 }, { tag => [ 'War' ] } ],
249             );
250            
251             LOCAL_SHIPS:
252             foreach my $ship (@{$ships_data->{ships}}) {
253            
254             next LOCAL_SHIPS
255             unless $ship->{type} eq 'fighter'
256             || $ship->{type} eq 'sweeper';
257            
258             next LOCAL_SHIPS
259             if $ship->{combat} < $self->min_defender_combat;
260            
261             given ($ship->{task}) {
262             when('Travelling') {
263             # Travelling home
264             next LOCAL_SHIPS
265             unless $ship->{to}{type} eq 'body'
266             && $ship->{to}{id} == $body_id;
267            
268             # Check arrival time
269             my $arrives = parse_date($ship->{date_arrives});
270             next LOCAL_SHIPS
271             if $arrives > $first_attacker_arrive;
272             }
273             when('Building') {
274             warn $ship;
275             # Check arrival time
276             my $arrives = parse_date($ship->{date_arrives});
277             next LOCAL_SHIPS
278             if $arrives > $first_attacker_arrive;
279             }
280             when('Docked') {
281             # do nothing
282             }
283             default {
284             next LOCAL_SHIPS;
285             }
286             }
287            
288             $count++;
289             }
290            
291             return $count;
292             }
293              
294             sub get_saws {
295             my ($self,$star_id,$first_attacker_arrive) = @_;
296            
297             my $count = 0;
298            
299             SYSTEM_PLANETS:
300             foreach my $planet_stats ($self->my_planets) {
301             next SYSTEM_PLANETS
302             if $planet_stats->{star_id} != $star_id;
303             my @saws = $self->find_building($planet_stats->{id},'SAW');
304             SAWS:
305             foreach my $saw (@saws) {
306             # Check SAW level
307             next SAWS
308             unless ($saw->{level} * 1000 * $saw->{efficiency} / 100) >= $self->min_defender_combat;
309            
310             # Check SAW availability
311             if (defined $saw->{work}) {
312             my $available = parse_date($saw->{work}{end});
313             next SAWS
314             if $available > $first_attacker_arrive;
315             }
316            
317             $count++;
318             }
319             }
320            
321             return $count;
322             }
323              
324             sub dispatch_defender {
325             my ($self,$to_body_id,$from_body_id,$count) = @_;
326            
327             my $spaceport = $self->find_building($from_body_id,'SpacePort');
328             return 0
329             unless $spaceport;
330             my $spaceport_object = $self->build_object($spaceport);
331            
332             # Get all available ships
333             my $ships_data = $self->request(
334             object => $spaceport_object,
335             method => 'view_all_ships',
336             params => [ { no_paging => 1 }, { tag => [ 'War' ] } ],
337             );
338            
339             my @relocate_ship;
340             my $dispatch_ship = 0;
341            
342             # Loop all war ships
343             LOCAL_SHIPS:
344             foreach my $ship (@{$ships_data->{ships}}) {
345             next LOCAL_SHIPS
346             unless $ship->{type} eq 'fighter'
347             || $ship->{type} eq 'sweeper';
348            
349             next LOCAL_SHIPS
350             if $ship->{name} =~ m/\!/;
351            
352             next LOCAL_SHIPS
353             if $ship->{type} eq 'sweeper'
354             && $ship->{name} !~ m/(dispatch|\$|\+)/;
355            
356             next LOCAL_SHIPS
357             if $ship->{combat} < $self->min_defender_combat;
358            
359             next LOCAL_SHIPS
360             unless $ship->{task} eq 'Docked';
361            
362             # Dispatch fighter directly
363             if ($ship->{type} eq 'fighter') {
364             $dispatch_ship++;
365             $self->request(
366             object => $spaceport_object,
367             method => 'send_ship',
368             params => [ $ship->{id}, { body_id => $to_body_id } ],
369             );
370             # Add sweeper to list of dispatchable units
371             } elsif ($ship->{type} eq 'sweeper') {
372             push(@relocate_ship,$ship->{id});
373             }
374            
375             # Check if we have enough defenders
376             return $dispatch_ship
377             if $dispatch_ship >= $count;
378             }
379            
380             # Relocate sweepers via push
381             my $relocateable_ships = min( ($count-$dispatch_ship) , scalar(@relocate_ship), $self->_planet_attack->{$to_body_id}{free_slots} );
382             if ($relocateable_ships > 0) {
383             my @relocate_ships_final = @relocate_ship[0..($relocateable_ships-1)];
384             $dispatch_ship += $self->push_ships($from_body_id,$to_body_id,\@relocate_ships_final);
385             }
386            
387             return $dispatch_ship;
388             }
389              
390             sub recall_defender {
391             my ($self,$body_id,$first_attacker_arrive) = @_;
392            
393             my $spaceport = $self->find_building($body_id,'SpacePort');
394             return 0
395             unless $spaceport;
396             my $spaceport_object = $self->build_object($spaceport);
397            
398             my $count = 0;
399            
400             # Get all available ships
401             my $ships_data = $self->request(
402             object => $spaceport_object,
403             method => 'recall_all',
404             );
405            
406             RECALL_SHIPS:
407             foreach my $ship (@{$ships_data->{ships}}) {
408             # Check if ship arrives on time
409             my $arrive = parse_date($ship->{ship}{date_arrives});
410             next RECALL_SHIPS
411             if $arrive > $first_attacker_arrive;
412             $count++;
413             }
414            
415             return $count;
416             }
417              
418             __PACKAGE__->meta->make_immutable;
419             no Moose;
420             1;