File Coverage

blib/lib/Games/Lacuna/Task/Action/Trade.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::Trade;
2              
3 1     1   1695 use 5.010;
  1         4  
  1         56  
4             our $VERSION = $Games::Lacuna::Task::VERSION;
5              
6 1     1   488 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 Games::Lacuna::Task::Utils qw(parse_ship_type);
12              
13             has 'trades' => (
14             is => 'rw',
15             isa => 'HashRef',
16             required => 1,
17             documentation => 'Automatic trades per planet [Required in config]',
18             );
19              
20             =pod
21              
22             Usually you will need to set up automatic trades in your config file to
23             use this action. The trade will only be created if you have the needed goods
24             on stock. Ships that are not on stock will be built.
25              
26             trade:
27             trades:
28             "[PLANET NAME OR ID]":
29             -
30             ask: [ESSENTIA ASKING]
31             offers:
32             -
33             class: "[ship|glyph|resource|plan|prisoner]"
34             type: "[NAME OF ITEM]"
35             quantity: [QUANTITY]
36             level: [PLAN LEVEL]
37             extra_build_level: [PLAN EXTRA BUILD LEVEL]
38             -
39             ...
40             -
41             ...
42              
43             Some example configurations:
44              
45             trade:
46             trades:
47             "Home Sweet Home":
48             -
49             ask: 3
50             offers:
51             -
52             class: "ship"
53             type: "Galleon"
54             quantity: 3
55             -
56             ask: 10
57             offers:
58             -
59             class: "plan"
60             type: "Geo Thermal Vent"
61             level: 1
62             extra_build_level: 1
63             -
64             class: "plan"
65             type: "Vulcano"
66             level: 1
67             -
68             class: "plan"
69             type: "Natural Spring"
70             level: 1
71             -
72             ask: 0.5
73             offers:
74             -
75             class: "resouce"
76             type: "trona"
77             quantity: 100000
78             -
79             class: "resouce"
80             type: "sulphur"
81             quantity: 100000
82              
83             =cut
84              
85             sub description {
86             return q[Add recurring trades to Trade Ministry];
87             }
88              
89             sub process_planet {
90             my ($self,$planet_stats) = @_;
91            
92             return
93             unless defined $self->trades->{$planet_stats->{name}}
94             || defined $self->trades->{$planet_stats->{id}};
95            
96             # Get trade ministry
97             my $tradeministry = $self->find_building($planet_stats->{id},'Trade');
98             return
99             unless $tradeministry;
100            
101             # Get trade ministry
102             my $tradeministry_object = $self->build_object($tradeministry);
103            
104             my $trades = $self->trades->{$planet_stats->{name}} || $self->trades->{$planet_stats->{id}};
105            
106             # Check if we have trades
107             return
108             unless scalar @{$trades};
109            
110             # Get current trade
111             my $trade_data = $self->paged_request(
112             object => $tradeministry_object,
113             method => 'view_my_market',
114             total => 'trade_count',
115             data => 'trades',
116             )->{trades};
117            
118             my @current_trades = _trade_serialize_response($trade_data);
119            
120             return
121             if scalar @current_trades >= $tradeministry->{level};
122            
123             my ($stored_resources,$stored_plans,$stored_glyphs,);
124            
125             # Loop all trades
126             TRADE:
127             foreach my $trade (@{$trades}) {
128             my @offer_data;
129             my $trade_complete = 1;
130             my $trade_identifier;
131             my %trade_identifier_parts;
132            
133             # Check offers
134             unless (defined $trade->{offers}
135             && ref $trade->{offers} eq 'ARRAY') {
136             $self->log('error','Invalid trade setting: Offers missing or invalid (%s)',$trade->{offers});
137             next TRADE;
138             }
139             unless (defined $trade->{ask}
140             && $trade->{ask} =~ m/^\d+(\.\d)?$/
141             && $trade->{ask} > 0) {
142             $self->log('error','Invalid trade setting: Ask missing or invalid (%s)',$trade->{ask});
143             next TRADE;
144             }
145            
146             # Build trade identifier
147             foreach my $offer (@{$trade->{offers}}) {
148             unless (defined $offer->{type}) {
149             $self->log('error','Invalid trade setting: Ask missing or invalid (%s)',$offer->{ask});
150             next TRADE;
151             }
152             $offer->{quantity} ||= 1;
153             my $trade_identifier_part;
154             if ($offer->{class} eq 'plan') {
155             $offer->{level} //= 1;
156             $offer->{extra_build_level} //= 0;
157             $trade_identifier_part = $offer->{class}.':'.$offer->{type}.':'.$offer->{level};
158             $trade_identifier_part .= '+'.$offer->{extra_build_level}
159             if $offer->{extra_build_level} > 0;
160             } elsif ($offer->{class} eq 'ship') {
161             $trade_identifier_part = $offer->{class}.':'.parse_ship_type($offer->{type});
162             } else {
163             $trade_identifier_part = $offer->{class}.':'.lc($offer->{type});
164             }
165             $trade_identifier_part = lc($trade_identifier_part);
166             $trade_identifier_parts{$trade_identifier_part} = $offer->{quantity};
167             }
168            
169             $trade_identifier = _trade_serialize($trade->{ask},%trade_identifier_parts);
170            
171             next TRADE
172             if $trade_identifier ~~ \@current_trades;
173            
174             # Check offer items
175             foreach my $offer (@{$trade->{offers}}) {
176            
177             given ($offer->{class}) {
178             when('ship') {
179             my @avaliable_ships = $self->get_ships(
180             planet => $planet_stats,
181             quantity => $offer->{quantity},
182             type => $offer->{type},
183             name_prefix => 'Trade',
184             );
185            
186             if (scalar @avaliable_ships == $offer->{quantity}) {
187             foreach my $ship (@avaliable_ships) {
188             push (@offer_data,{
189             "type" => "ship",
190             "ship_id" => $ship,
191             });
192             }
193             } else {
194             $trade_complete = 0;
195             }
196             }
197             when ('plan') {
198             $stored_plans ||= $self->request(
199             object => $tradeministry_object,
200             method => 'get_plans',
201             )->{plans};
202            
203             my $needed_quantity = $offer->{quantity};
204             PLAN:
205             foreach my $plan (@{$stored_plans}) {
206             if (lc($plan->{name}) eq lc($offer->{type})
207             && $plan->{level} == $offer->{level}
208             && $plan->{extra_build_level} == $offer->{extra_build_level}) {
209             push (@offer_data,{
210             "type" => "plan",
211             "plan_id" => $plan->{id},
212             });
213             $needed_quantity --;
214             last PLAN
215             if $needed_quantity == 0;
216             }
217             }
218             $trade_complete = 0
219             unless ($needed_quantity == 0);
220             }
221             when ('glyph') {
222             $stored_glyphs ||= $self->request(
223             object => $tradeministry_object,
224             method => 'get_glyphs',
225             )->{glyphs};
226            
227             my $needed_quantity = $offer->{quantity};
228             GLYPH:
229             foreach my $glyph (@{$stored_glyphs}) {
230             if (lc($glyph->{type}) eq lc($offer->{type})) {
231             push (@offer_data,{
232             "type" => "glyph",
233             "glyph_id" => $glyph->{id},
234             });
235             $needed_quantity --;
236             last GLYPH
237             if $needed_quantity == 0;
238             }
239             }
240             $trade_complete = 0
241             unless ($needed_quantity == 0);
242             }
243             when ('resource') {
244             $stored_resources ||= $self->request(
245             object => $tradeministry_object,
246             method => 'get_stored_resources',
247             )->{resources};
248            
249             unless (defined $stored_resources->{$offer->{type}}) {
250             $self->log('error','Invalid trade setting: Unknown resource type (%s)',$trade->{type});
251             next TRADE;
252             }
253            
254             if ($stored_resources->{$offer->{type}} > $offer->{quantity}) {
255             push (@offer_data,{
256             "type" => $offer->{type},
257             "quantity" => $offer->{quantity},
258             });
259             } else {
260             $trade_complete = 0;
261             }
262             }
263             when ('prisoner') {
264             $self->log('warn','Prisoner trade class not implemented yet');
265             }
266             default {
267             $self->log('error','Invalid trade setting: Unknown offer class (%s)',$_);
268             next TRADE;
269             }
270             }
271             }
272            
273             # Add trade to market
274             if ($trade_complete) {
275            
276             # Get trade ship
277             my $trade_ships = $self->trade_ships($planet_stats->{id},\@offer_data);
278             my @trade_ships = keys %{$trade_ships};
279            
280             next TRADE
281             unless scalar @trade_ships == 1;
282            
283             my $response = $self->request(
284             object => $tradeministry_object,
285             method => 'add_to_market',
286             params => [ \@offer_data, $trade->{ask}, { ship_id => $trade_ships[0] } ]
287             );
288             $self->log('notice','Adding trade on %s',$planet_stats->{name});
289             }
290             }
291             }
292              
293             sub _trade_serialize {
294             my ($ask,%offer) = @_;
295            
296             my @trade_identifier_parts =
297             map { lc($_).'='.$offer{$_} }
298             grep { $offer{$_} > 0 }
299             sort
300             keys %offer;
301            
302             push(@trade_identifier_parts,'ask='.sprintf('%.1f',$ask));
303            
304             return join(';',@trade_identifier_parts);
305             }
306              
307             sub _trade_serialize_response {
308             my ($trades) = @_;
309            
310             my @trade_identifiers;
311            
312             foreach my $trade (@{$trades}) {
313             my %trade_serialize;
314             foreach my $offer (@{$trade->{offer}}) {
315             my ($moniker,$quantity);
316             given ($offer) {
317             when (/^(?<quantity>[0-9,]+)\s(?<type>\w+)$/) {
318             $moniker = 'resource:'.$+{type};
319             $quantity = $+{quantity};
320             $quantity =~ s/,//g;
321             }
322             when (/^(?<type>\w+)\sglyph$/) {
323             $moniker = 'glyph:'.$+{type};
324             $quantity = 1;
325             }
326             when (/^(?<type>[[:alpha:][:space:]]+)\s\(.+\)$/) {
327             $moniker = 'ship:'.parse_ship_type($+{type});
328             $quantity = 1;
329             }
330             when (/^(?<type>[[:alpha:][:space:]]+)\s\((?<level>[^\)]+)\)\splan$/) {
331             $moniker = 'plan:'.lc($+{type}).':'.$+{level};
332             $quantity = 1;
333             }
334             when (/^Level\s(?<level>\d+)\sspy\snamed\s[^(]\(prisoner\)/) {
335             $moniker = 'prisoner:'.lc($+{level});
336             $quantity = 1;
337             }
338             default {
339             warn("Unkown offer: $_");
340             }
341             }
342             $trade_serialize{$moniker} ||= 0;
343             $trade_serialize{$moniker} += $quantity;
344             }
345            
346             push(@trade_identifiers,_trade_serialize($trade->{ask},%trade_serialize));
347             }
348            
349             return @trade_identifiers;
350             }
351              
352             __PACKAGE__->meta->make_immutable;
353             no Moose;
354             1;