package Lacuna::Web::Admin; use Moose; use utf8; no warnings qw(uninitialized); extends qw(Lacuna::Web); use Lacuna::Constants qw(FOOD_TYPES ORE_TYPES); use Switch::Right; use Module::Find; use UUID::Tiny ':std'; use Lacuna::Util qw(format_date commify kmbtq); use List::Util qw(sum); use Data::Dumper; use LWP::UserAgent; sub www_send_test_message { my ($self, $request, $id) = @_; $id ||= $request->param('empire_id'); my $empire = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->find($id); unless (defined $empire) { confess [404, 'Empire not found.']; } if ($empire->id <= 1) { confess [400, 'That empire is required.']; } $empire->send_message( from => $empire, body => 'This is a test message that contains all the components possible in a message. {food} {water} {ore} {energy} {waste} {happiness} {essentia} {build} {time} {Empire 1 Lacuna Expanse Corp} {Planet '.$empire->home_planet->id.' '.$empire->home_planet->name.'} {Alliance 1 Fake Alliance} {Starmap 0 0 The Center of the Map} [https://tlecommunity.com] ', subject => 'Test Message', tags => ['Alert'], attachments => { table => [ ['Header 1', 'Header 2'], ['Row 1 Field 1', 'Row 1 Field 2'], ['Row 2 Field 1', 'Row 2 Field 2'], ], image => { url => 'http://bloximages.chicago2.vip.townnews.com/host.madison.com/content/tncms/assets/editorial/8/ec/604/8ec6048a-998e-11de-b821-001cc4c002e0.preview-300.jpg', title => 'JT Rocks', link => 'http://host.madison.com/wsj/business/article_bd9f8c96-998d-11de-87d3-001cc4c002e0.html', }, link => { url => 'http://www.plainblack.com/', label => 'Plain Black', }, map => { surface => 'surface-p12', buildings => [ { x => 0, y => 0, image => 'command4', }, { x => -4, y => 2, image => 'apples9', }, ] } } ); return $self->wrap('Sent!'); } sub www_search_essentia_codes { my ($self, $request) = @_; my $page_number = $request->param('page_number') || 1; my $codes = Lacuna->db->resultset('Lacuna::DB::Result::EssentiaCode')->search(undef, {order_by => { -desc => 'date_created' }, rows => 25, page => $page_number }); my $code = $request->param('code') || ''; if ($code) { $codes = $codes->search({code => { like => $code.'%' }}); } my $used = $request->param('used'); if ( defined $used && length $used ) { $codes = $codes->search({used => $used}); } my $toggle_used = $used ? '0' : 1; my $out = '

Search Essentia Codes

'; $out .= '
'; $out .= sprintf('', $code, $toggle_used ); while (my $code = $codes->next) { $out .= sprintf('', $code->id, $code->code, $code->amount, $code->description, $code->date_created, $code->used); } $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= '
IdCodeAmountDescriptionDate CreatedUsed
%s%s%s%s%s%s
'; my %page_query = ( code => $code, used => $used, ); $out .= $self->format_complex_paginator('search/essentia/codes', \%page_query, $page_number); return $self->wrap($out); } sub www_add_essentia_code { my ($self, $request) = @_; my $code = Lacuna->db->resultset('Lacuna::DB::Result::EssentiaCode')->new({ date_created => DateTime->now, amount => $request->param('amount'), description => decode_utf8($request->param('description')), code => create_uuid_as_string(UUID_V4), })->insert; return $self->wrap('

Essentia Code: '. $code->code.'

Back To Essentia Codes'); } sub www_view_essentia_log { my ($self, $request) = @_; my $empire_id = $request->param('empire_id'); my $transactions = Lacuna->db->resultset('Lacuna::DB::Result::Log::Essentia')->search({empire_id => $empire_id}, {order_by => { -desc => 'date_stamp' }}); my $out = '

Essentia Transaction Log

'; $out .= sprintf('Back To Empire', $empire_id); $out .= ''; while (my $transaction = $transactions->next) { my $empire_link = ''; if ( my $from_empire_id = $transaction->from_id ) { $empire_link = sprintf '%d', $from_empire_id, $from_empire_id; } $out .= sprintf('', $transaction->date_stamp, $transaction->amount, $transaction->description, $empire_link, $transaction->from_name, $transaction->transaction_id); } $out .= '
DateAmountDescriptionFrom IDFromTransaction ID
%s%s%s%s%s%s
'; return $self->wrap($out); } sub www_view_login_log { my ($self, $request) = @_; my ( $search_field, $search_value ); for my $field (qw( empire_id ip_address api_key )) { if ( my $value = $request->param($field) ) { $search_field = $field; $search_value = $value; last; } } my $page_number = $request->param('page_number') || 1; my $logins = Lacuna->db->resultset('Lacuna::DB::Result::Log::Login')->search( { $search_field => $search_value }, { order_by => { -desc => 'date_stamp' }, rows => 25, page => $page_number, }); my $out = '

Login Log

'; if ( $search_field eq 'empire_id' ) { $out .= sprintf('Back To Empire', $search_value); } $out .= ''; while (my $login = $logins->next) { my $sitter = $login->is_sitter ? 'Sitter' : ''; $out .= sprintf('', $login->empire_id, $login->empire_id); $out .= sprintf('', $login->empire_name, $login->date_stamp, $login->log_out_date, $login->extended ); $out .= sprintf('', $login->ip_address, $login->ip_address ); $out .= sprintf('', $sitter); $out .= sprintf('', $login->api_key, $login->api_key ); $out .= ''; } $out .= '
IDEmpire NameLog-in DateLog-out DateExtendedIP AddressSitterAPI Key
%d%s%s%s%s%s%s%s
'; $out .= $self->format_paginator('view/login/log', $search_field, $search_value, $page_number); return $self->wrap($out); } sub www_view_empire_name_change_log { my ($self, $request) = @_; my $empire_id = $request->param('empire_id'); my $history = Lacuna->db->resultset('Lacuna::DB::Result::Log::EmpireNameChange')->search({empire_id => $empire_id},{order_by => { -desc => 'date_stamp' }}); my $out = '

Empire Name-Change Log

'; $out .= sprintf('Back To Empire', $empire_id); $out .= ''; while (my $log = $history->next) { $out .= sprintf('', $log->date_stamp, $log->empire_name, $log->old_empire_name); } $out .= '
DateNew NameOld Name
%s%s%s
'; return $self->wrap($out); } sub www_search_similar_empire { my ($self, $request) = @_; my $empire_id = $request->param('empire_id'); my $page_number = $request->param('page_number') || 1; my $type = $request->param('type'); my $empire = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->find($empire_id); unless (defined $empire) { confess [404, 'Empire not found.']; } my @query = ( id => { '!=' => $empire_id }, ); if ( $type eq 'name' ) { my @words = $empire->name =~ /(\p{Alpha}+)/g; if ( @words ) { push @query, -or => [ map { my %x = ( LIKE => "%$_%" ); name => \%x } @words ]; } else { my $name = $empire->name; push @query, name => { 'LIKE' => "%$name%" }; } } elsif ( $type eq 'email_user' ) { my ($user) = $empire->email =~ /([^@]+)/; if ( !defined $user ) { confess [ 400, 'Failed to parse email address' ]; } my @words = $user =~ /(\p{Alpha}+)/g; if ( @words ) { push @query, -or => [ map { my %x = ( LIKE => "%$_%\@%" ); email => \%x } @words ]; } else { my $email = $empire->email; push @query, email => { 'LIKE' => "%$email%" }; } } elsif ( $type eq 'email_domain' ) { my ($domain) = $empire->email =~ /@([^@]+)/; if ( !defined $domain ) { confess [ 400, 'Failed to parse email address' ]; } push @query, email => { 'LIKE' => "%\@$domain" }; } my $search = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->search( { -and => \@query }, { order_by => { -desc => 'id' }, rows => 25, page => $page_number, }); my $out = '

Similar Empires

'; $out .= sprintf('Back To Empire', $empire_id); $out .= ''; while (my $match = $search->next) { $out .= sprintf('', $match->id, $match->id); $out .= sprintf('', $match->name, $match->email, $match->date_created, $match->last_login ); } $out .= '
IDEmpire NameEmailCreatedLast Login
%d%s%s%s%s
'; $out .= $self->format_complex_paginator('search/similar/empire', { empire_id => $empire_id, type => $type }, $page_number); return $self->wrap($out); } sub www_search_empires { my ($self, $request) = @_; my $page_number = $request->param('page_number') || 1; my $empires = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->search(undef, { rows => 25, page => $page_number }); my $field = $request->param('field') || 'name'; my $name = decode_utf8($request->param('name') || ''); if ($name) { my $query = "$name%"; $query =~ s/\*/%/; $empires = $empires->search({$field => { like => $query }}); } my $order = $request->param('order') || 'name'; my $desc = $request->param('desc') || 0; if ( smartmatch($order, any => [qw( id name last_login )]) ) { my $sort = $desc ? "-desc" : "-asc"; $empires = $empires->search(undef, { order_by => {$sort => $order} }); } my $out = '

Search Empires

'; $out .= '
'; $out .= '
'; $out .= ''; $out .= sprintf('', $name, $name ); $out .= sprintf('', $name, $name ); $out .= ''; $out .= sprintf('', $name, $name ); while (my $empire = $empires->next) { $out .= sprintf('', $empire->id, $empire->id, $empire->name, $empire->species_name, $empire->home_planet_id, $empire->home_planet_id, $empire->last_login); } $out .= '
Id ⇓ ⇑Name ⇓ ⇑SpeciesHomeLast Login ⇓ ⇑
%s%s%s%s%s
'; my %page_query = ( name => $name, order => $order, desc => $desc, ); $out .= $self->format_complex_paginator('search/empires', \%page_query, $page_number); return $self->wrap($out); } use Encode; sub www_search_bodies { my ($self, $request) = @_; my $page_number = $request->param('page_number') || 1; my $bodies = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->search(undef, {order_by => ['me.name'], rows => 25, page => $page_number, prefetch=>[qw/empire star/] }); my $name = decode_utf8($request->param('name') || ''); my $pager = 'name'; if ($name) { my $query = "$name%"; $query =~ s/\*/%/g; $bodies = $bodies->search({'me.name' => { like => $query }}); } if ($request->param('empire_id')) { $pager = 'empire_id'; $name = $request->param('empire_id'); $bodies = $bodies->search({'me.empire_id' => $name}); } if ($request->param('zone')) { $bodies = $bodies->search({'me.zone' => $request->param('zone')}); } if ($request->param('star_id')) { $bodies = $bodies->search({'me.star_id' => $request->param('star_id')}); } my $out = '

Search Bodies

'; $out .= '
'; $out .= ''; while (my $body = $bodies->next) { $out .= sprintf('', $body->id, $body->id, $body->name, $body->x, $body->y, $body->zone, $body->star_id, $body->star->name,$body->star_id, $body->orbit, $body->image_name, kmbtq($body->happiness), $body->empire_id || '', $body->empire_id ? sprintf("%s (%s)",$body->empire->name,$body->empire_id) : '' ); } $out .= '
IdNameXYZoneStarOTypeHappinessEmpire
%s%s%s%s%s%s (%d)%s%s%s%s
'; $out .= $self->format_paginator('search/bodies', $pager, $name, $page_number); return $self->wrap($out); } sub www_search_stars { my ($self, $request) = @_; my $page_number = $request->param('page_number') || 1; my $stars = Lacuna->db->resultset('Lacuna::DB::Result::Map::Star')->search(undef, {order_by => ['name'], rows => 25, page => $page_number }); my $name = decode_utf8($request->param('name') || ''); if ($name) { my $query = "$name%"; $query =~ s/\*/%/; $stars = $stars->search({name => { like => $query }}); } if ($request->param('zone')) { $stars = $stars->search({zone => $request->param('zone')}); } my $out = '

Search Stars

'; $out .= '
'; $out .= ''; while (my $star = $stars->next) { $out .= sprintf('', $star->id, $star->id, $star->name, $star->x, $star->y, $star->zone, $star->station_id || '', $star->station_id || ''); } $out .= '
IdNameXYZoneStation
%s%s%s%s%s%s
'; $out .= $self->format_paginator('search/stars', 'name', $name, $page_number); return $self->wrap($out); } sub www_complete_builds { my ($self, $request, $body_id) = @_; $body_id ||= $request->param('body_id'); my $body = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->find($body_id); foreach my $building (@{$body->building_cache}) { next unless ( $building->is_upgrading ); $building->finish_upgrade; } return $self->wrap(sprintf('All building construction completed! Back To Body', $request->param('body_id'))); } sub www_send_stellar_flare { my ($self, $request, $body_id) = @_; $body_id ||= $request->param('body_id'); my $body = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->find($body_id); foreach my $building (@{$body->building_cache}) { # next unless ('Infrastructure' ~~ [$building->build_tags]); next if ( $building->class eq 'Lacuna::DB::Result::Building::PlanetaryCommand' ); $building->efficiency(0); $building->update; } $body->needs_recalc(1); $body->needs_surface_refresh(1); $body->update; $body->add_news(99, '%s has just belched a massive stellar flare. %s bore the brunt of it.', $body->star->name, $body->name); $body->empire->send_message( subject => 'Stellar Flare', body => "A stellar flare has disabled most of the infrastructure on ".$body->name.".\n\nRegards,\n\nYour Humble Assistant", tag => 'Alert', ); return $self->wrap('Stellar flare sent!'); } sub www_send_meteor_shower { my ($self, $request, $body_id) = @_; $body_id ||= $request->param('body_id'); my $body = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->find($body_id); foreach my $building (@{$body->building_cache}) { next unless (smartmatch('Infrastructure', any => [$building->build_tags])); # next if ( $building->class eq 'Lacuna::DB::Result::Building::PlanetaryCommand' ); $building->class('Lacuna::DB::Result::Building::Permanent::Crater'); $building->level(1); $building->is_upgrading(0); $building->is_working(0); $building->update; } $body->needs_recalc(1); $body->needs_surface_refresh(1); $body->update; $body->add_news(99, 'A meteor shower rained hell on %s today, and much of its infrastructure was destroyed.', $body->name); $body->empire->send_message( subject => 'Meteor Shower', body => "A meteor shower has just destroyed most of the infrastructure on ".$body->name.".\n\nRegards,\n\nYour Humble Assistant", tag => 'Alert', ); return $self->wrap('Meteor shower sent!'); } sub www_send_pestilence { my ($self, $request, $body_id) = @_; $body_id ||= $request->param('body_id'); my $body = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->find($body_id); if ($body->id == $body->empire->home_planet_id) { confess [401, 'You cannot send pestilence to someone\'s home planet.']; } $body->add_news(99, 'Yesterday there was an outbreak of Derni Pestilence on %s. Today %s has gone dark.', $body->name, $body->name); $body->empire->send_message( subject => 'Pestilence', body => "Derni Pestilence has broken out on ".$body->name.". The colony is lost.\n\nRegards,\n\nYour Humble Assistant", tag => 'Alert', ); my @all_buildings = @{$body->building_cache}; $body->delete_buildings(\@all_buildings); $body->sanitize; return $self->wrap('Pestilence sent!'); } sub www_view_buildings { my ($self, $request, $body_id) = @_; $body_id ||= $request->param('body_id'); my $buildings = Lacuna->db->resultset('Lacuna::DB::Result::Building')->search({ body_id => $body_id }, {order_by => ['x','y'] }); my $out = '

View Buildings

'; $out .= sprintf('Back To Body', $body_id); $out .= ''; while (my $building = $buildings->next) { $out .= sprintf(''); $out .= sprintf('',$building->id,$building->name); $out .= sprintf('',$building->x); $out .= sprintf('',$building->y); $out .= sprintf('',$building->level); $out .= sprintf(''); $out .= sprintf(''); $out .= sprintf('', $building->id); $out .= sprintf(''); } $out .= '
IdNameXYLevelInProgressEfficiency
%s%s%s',$building->is_upgrading, $building->id); $out .= sprintf('', $building->efficiency); $out .= sprintf('
'; $out .= '

Add Building

'; $out .= '

This costs no resources or plans, and bypasses normal restrictions '; $out .= 'such as tech-level, plot-count, etc.
'; $out .= 'Level is the final level after the build is complete.
'; $out .= 'X and Y are not required.

'; $out .= '
'; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= '
TypeXYLevelSkip build queue
'; return $self->wrap($out); } sub www_add_building { my ($self, $request) = @_; my $body = Lacuna->db->resultset('Map::Body')->find($request->param('body_id')); unless (defined $body) { confess [404, 'Body not found.']; } my $class = $request->param('class'); my $x = $request->param('x'); my $y = $request->param('y'); my $level = $request->param('level') || 1; $level--; if ( !length $x || !length $y ) { ($x, $y) = $body->find_free_space; } # check the plot lock if ($body->is_plot_locked($x, $y)) { confess [1013, "That plot is reserved for another building.", [$x,$y]]; } else { $body->lock_plot($x,$y); } # is the plot empty? $body->check_for_available_build_space( $x, $y ); my $building = Lacuna->db->resultset('Lacuna::DB::Result::Building')->new({ x => $x, y => $y, level => $level, body_id => $body->id, body => $body, class => $class, }); $body->build_building( $building ); if ( $request->param('skip_build_queue') ) { $building->finish_upgrade; } return $self->www_view_buildings($request, $body->id); } sub www_set_efficiency { my ($self, $request) = @_; my $building = Lacuna->db->resultset('Lacuna::DB::Result::Building')->find($request->param('building_id')); my $body = Lacuna->db->resultset('Map::Body')->find($building->body_id); my $x = $request->param('x'); my $y = $request->param('y'); # is the building being moved? if ( $x != $building->x || $y != $building->y ) { # check the plot lock if ($body->is_plot_locked($x, $y)) { confess [1013, "That plot is reserved for another building.", [$x,$y]]; } else { $body->lock_plot($x,$y); } # is the plot empty? $body->check_for_available_build_space( $x, $y ); } $building->update({ efficiency => $request->param('efficiency'), x => $x, y => $y, level => $request->param('level'), }); return $self->www_view_buildings($request, $building->body_id); } sub www_delete_building { my ($self, $request) = @_; my $building = Lacuna->db->resultset('Lacuna::DB::Result::Building')->find($request->param('building_id')); my $body = $building->body; $building->delete; $body->needs_recalc(1); $body->needs_surface_refresh(1); $body->update; $body->tick; return $self->www_view_buildings($request, $building->body_id); } sub www_view_ships { my ($self, $request, $body_id) = @_; $body_id ||= $request->param('body_id'); my $ships = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->search({ body_id => $body_id }); my $out = '

View Ships

'; $out .= sprintf('Back To Body', $body_id); $out .= ''; while (my $ship = $ships->next) { $out .= sprintf('', $ship->id, $ship->name, $ship->type_formatted, $ship->stealth, $ship->hold_size, $ship->speed, $ship->combat); if ($ship->task eq 'Travelling') { $out .= sprintf('', $ship->task, $ship->id, $body_id); } elsif (smartmatch($ship->task, any => [qw(Defend Orbiting)])) { my $target = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->find($ship->foreign_body_id); $out .= sprintf('', $ship->task, $target->name, $target->x, $target->y, $ship->id, $body_id); } elsif ($ship->task ne 'Docked') { $out .= sprintf('', $ship->task, $ship->id, $body_id); } else { $out .= sprintf('', $ship->task); } $out .= sprintf('', $ship->id, $body_id); } $out .= '
IdNameTypeStealthHold SizeSpeedCombatTaskDelete
%s%s%s%s%s%s%s%s
%s
%s (%d, %d)
%s
%s
'; return $self->wrap($out); } sub www_zoom_ship { my ($self, $request) = @_; my $ship_id = $request->param('ship_id'); my $ship = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->find($ship_id); if ($ship) { # my $body = $ship->body; # $ship->re_schedule(DateTime->now); $ship->date_available(DateTime->now); $ship->update; # $ship->update({date_available => DateTime->now}); # $body->tick; } return $self->www_view_ships($request); } sub www_recall_ship { my ($self, $request) = @_; my $ship_id = $request->param('ship_id'); my $ship = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->find($ship_id); my $target = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->find($ship->foreign_body_id); my $body = $ship->body; $ship->send( target => $target, direction => 'in', ); $body->tick; return $self->www_view_ships($request); } sub www_dock_ship { my ($self, $request) = @_; my $ship_id = $request->param('ship_id'); my $ship = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->find($ship_id); $ship->land->update; return $self->www_view_ships($request); } sub www_delete_ship { my ($self, $request) = @_; my $ship = Lacuna->db->resultset('Lacuna::DB::Result::Ships')->find($request->param('ship_id')); $ship->delete; return $self->www_view_ships($request); } sub www_view_resources { my ($self, $request, $body_id) = @_; $body_id ||= $request->param('body_id'); my $body = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->find($request->param('body_id')); my @types = (FOOD_TYPES, ORE_TYPES, qw(water energy waste)); my $out = '

View Resources

'; $out .= sprintf('Back To Body', $body_id); $out .= ''; foreach my $resource (@types) { $out .= sprintf('', $resource, $body->type_stored($resource), $body_id, $resource); } $out .= '
TypeStoredAdd
%s%s
'; return $self->wrap($out); } sub www_add_resources { my ($self, $request) = @_; my $body = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->find($request->param('body_id')); unless (defined $body) { confess [404, 'Body not found.']; } $body->add_type($request->param('resource'), $request->param('amount')); $body->update; return $self->www_view_resources($request, $body->id); } sub www_view_glyphs { my ($self, $request, $body_id) = @_; $body_id ||= $request->param('body_id'); my $glyphs = Lacuna->db->resultset('Lacuna::DB::Result::Glyph')->search({ body_id => $body_id }, {order_by => ['type'] }); my $out = '

View Glyphs

'; $out .= sprintf('Back To Body', $body_id); $out .= ''; while (my $glyph = $glyphs->next) { $out .= sprintf('', $glyph->id, $glyph->type, $glyph->quantity, $body_id, $glyph->id); } $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= '
IdTypeQuantityAction
%s%s%sDelete
'; return $self->wrap($out); } sub www_add_glyph { my ($self, $request) = @_; my $body = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->find($request->param('body_id')); unless (defined $body) { confess [404, 'Body not found.']; } $body->add_glyph($request->param('type'), $request->param('quantity')); return $self->www_view_glyphs($request, $body->id); } sub www_delete_glyph { my ($self, $request) = @_; my $body = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->find($request->param('body_id')); unless (defined $body) { confess [404, 'Body not found.']; } $body->glyph->find($request->param('glyph_id'))->delete; return $self->www_view_glyphs($request, $body->id); } sub www_view_plans { my ($self, $request, $body_id) = @_; $body_id ||= $request->param('body_id'); my $body = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->find($request->param('body_id')); my $plans = $body->sorted_plans; my $out = '

View Plans

'; $out .= sprintf('Back To Body', $body_id); $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; for my $plan (@$plans) { $out .= sprintf('
LevelNameExtra Build LevelQuantityAction
%s%s%s%s',$plan->level, $plan->class->name, $plan->extra_build_level, $plan->quantity); $out .= sprintf('
'); $out .= sprintf('',$plan->level); $out .= sprintf('',$plan->class); $out .= sprintf('',$plan->extra_build_level); $out .= sprintf('',$body_id); $out .= sprintf(''); $out .= sprintf(''); $out .= sprintf('
'); } $out .= '
'; return $self->wrap($out); } sub www_add_plan { my ($self, $request) = @_; my $body = Lacuna->db->resultset('Map::Body')->find($request->param('body_id')); unless (defined $body) { confess [404, 'Body not found.']; } $body->add_plan($request->param('class'), $request->param('level'), $request->param('extra_build_level'), $request->param('quantity')); return $self->www_view_plans($request, $body->id); } sub www_delete_plan { my ($self, $request) = @_; my $body = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->find($request->param('body_id')); unless (defined $body) { confess [404, 'Body not found.']; } # Find a plan my ($plan) = grep { $_->level == $request->param('level') and $_->class eq $request->param('class') and $_->extra_build_level == $request->param('extra') } @{$body->plan_cache}; if (not defined $plan) { confess [404, 'Plan not found.']; } if ($request->param('delete_one')) { $body->delete_one_plan($plan); } if ($request->param('delete_all')) { $body->delete_many_plans($plan, $plan->quantity); } return $self->www_view_plans($request, $body->id); } sub www_recalc_body { my ($self, $request) = @_; my $body = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->find($request->param('body_id')); unless (defined $body) { confess [404, 'Body not found.']; } $body->update({needs_recalc=>1}); return $self->wrap(sprintf('Done! Back To Body', $request->param('body_id'))); } sub format_paginator { my ($self, $method, $key, $value, $page_number) = @_; return $self->format_complex_paginator( $method, { $key => $value }, $page_number ); } sub format_complex_paginator { my ($self, $method, $query, $page_number) = @_; my $out = '
Page: '.$page_number.''; my $query_str = join ';', map { sprintf "%s=%s", $_, $query->{$_} } keys %$query; $out .= '< Previous | '; $out .= 'Next > '; $out .= '
'; for my $key ( keys %$query ) { $out .= sprintf '', $key, $query->{$key}; } $out .= '
'; $out .= '
'; return $out; } =for later MUCH later. sub www_delete_empire { my ($self, $request, $id) = @_; $id ||= $request->param('empire_id'); my $empire = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->find($id); unless (defined $empire) { confess [404, 'Empire not found.']; } unless ($empire->self_destruct_active) { if ($empire->id <= 1) { confess [400, 'That empire is required.']; } } $empire->delete; return $self->www_search_empires($request); } =cut sub www_toggle_verified { my ($self, $request, $id) = @_; $id ||= $request->param('id'); my $empire = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->find($id); unless (defined $empire) { confess [404, 'Empire not found.']; } $empire->update({is_verified => $empire->is_verified ? 0 : 1}); $empire->clear_rpc_limit_cache; return $self->www_view_empire($request, $id); } sub www_toggle_isolationist { my ($self, $request, $id) = @_; $id ||= $request->param('id'); my $empire = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->find($id); unless (defined $empire) { confess [404, 'Empire not found.']; } if ($empire->is_isolationist) { $empire->update({is_isolationist => 0}); } else { $empire->update({is_isolationist => 1}); } return $self->www_view_empire($request, $id); } =for probably never Admins are added/removed so rarely, it shouldn't be done so trivially sub www_toggle_admin { my ($self, $request, $id) = @_; $id ||= $request->param('id'); my $empire = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->find($id); unless (defined $empire) { confess [404, 'Empire not found.']; } if ($empire->is_admin) { $empire->update({is_admin => 0}); } else { $empire->update({is_admin => 1}); } return $self->www_view_empire($request, $id); } =cut sub www_toggle_mission_curator { my ($self, $request, $id) = @_; $id ||= $request->param('id'); my $empire = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->find($id); unless (defined $empire) { confess [404, 'Empire not found.']; } if ($empire->is_mission_curator) { $empire->update({is_mission_curator => 0}); } else { $empire->update({is_mission_curator => 1}); } return $self->www_view_empire($request, $id); } sub www_become_empire { my ($self, $request, $id) = @_; $id ||= $request->param('empire_id'); my $empire = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->find($id); unless (defined $empire) { confess [404, 'Empire not found.']; } my $uri = Lacuna->config->get('server_url'); $uri .= 'app/#session_id=%s'; $uri = sprintf $uri, $empire->start_session({ is_admin => $request->user, api_key => 'admin:' . $request->user, request => $request })->id; [$uri, { status => 302 } ] } # Generates a short random password from characters that are hard to confuse # when read aloud or copied by hand (no 0/O, 1/l/I). sub generate_temporary_password { my ($self, $length) = @_; $length ||= 10; my @chars = ('a'..'k', 'm', 'n', 'p'..'z', 'A'..'H', 'J'..'N', 'P'..'Z', 2..9); my $limit = 256 - (256 % @chars); # reject bytes past this to avoid modulo bias open my $fh, '<:raw', '/dev/urandom' or confess [500, "Could not open /dev/urandom: $!"]; my $password = ''; while (length $password < $length) { read($fh, my $bytes, 32) == 32 or confess [500, 'Could not read /dev/urandom.']; for my $byte (unpack 'C*', $bytes) { next if $byte >= $limit; $password .= $chars[$byte % @chars]; last if length $password == $length; } } close $fh; return $password; } sub www_reset_empire_password { my ($self, $request, $id) = @_; unless ($request->method eq 'POST') { confess [405, 'Password resets must be submitted from the Manage Empire page.']; } $id ||= $request->param('empire_id'); my $empire = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->find($id); unless (defined $empire) { confess [404, 'Empire not found.']; } if ($empire->id <= 1) { confess [400, 'That empire is required.']; } my $password = $self->generate_temporary_password; $empire->password($empire->encrypt_password($password)); $empire->password_recovery_key(''); # invalidate any outstanding reset link $empire->update; my $sent = 0; if ($empire->email) { $sent = $empire->send_email( 'Your Password Has Been Reset', sprintf("An administrator has reset the password for your empire, %s.\n\nYour new password is: %s\n\nLog in at %s and change it from your empire profile.", $empire->name, $password, Lacuna->config->get('server_url')), ); } my $out = '

Password Reset

'; $out .= sprintf('

The password for %s has been reset.

', $empire->id, $empire->name); if ($sent) { $out .= sprintf('

The new password was emailed to %s.

', $empire->email); } else { $out .= $empire->email ? sprintf('

Sending the email to %s failed, so send this password to them yourself:

', $empire->email) : '

This empire has no email address, so send this password to them yourself:

'; $out .= sprintf('

%s

', $password); } return $self->wrap($out); } sub www_view_empire { my ($self, $request, $id) = @_; $id ||= $request->param('id'); my $empire = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->find($id); unless (defined $empire) { confess [404, 'Empire not found.']; } my $out = '

Manage Empire

'; $out .= ''; if ( $empire->self_destruct_active ) { $out .= sprintf('', $empire->self_destruct_date); } $out .= sprintf('', $empire->id); $out .= sprintf('', $empire->rpc_count || 0, $empire->rpc_limit); $out .= sprintf('',$empire->id); $out .= sprintf(''; $out .= sprintf('', $empire->date_created); $out .= sprintf('', $empire->stage); $out .= sprintf('',$empire->id); $out .= sprintf('',$empire->id); $out .= sprintf('', $empire->essentia_free, $empire->essentia_game, $empire->essentia_paid); $out .= sprintf('', $empire->id); $out .= sprintf('', $empire->species_name); $out .= sprintf('', $empire->home_planet_id, $empire->home_planet->name, $empire->home_planet_id); $out .= sprintf(''); $out .= ''; $out .= ''; $out .= sprintf('', $empire->description); $out .= sprintf('', $empire->university_level, $empire->id); $out .= sprintf('', $empire->is_isolationist, $empire->id); $out .= sprintf('', $empire->is_admin); $out .= sprintf('', $empire->is_verified, $empire->id); $out .= sprintf('', $empire->is_mission_curator, $empire->id); my $notes = Lacuna->db->resultset('Log::EmpireAdminNotes')->find({empire_id => $empire->id},{order_by => { -desc => 'id' }, rows => 1 }); $out .= sprintf('', $empire->id, $notes ? $notes->notes : '', $notes ? $notes->creator : 'not set yet', $notes ? $notes->date_stamp : 'not set yet', $empire->id ); $out .= '
Self Destruct Active!Expires: %s
Id%s
RPC Requests%s / %s
Name%s', $empire->name); $out .= sprintf('View History',$empire->id); $out .= sprintf(' | Find Similar Empire Names
Email%s', $empire->email); if ( $empire->email ) { $out .= sprintf('Find Similar Email Usernames',$empire->id); $out .= sprintf(' | Find Same Email Domains',$empire->id); } $out .= '
Created%s
Stage%s
Last Login%s', $empire->last_login); $out .= sprintf('View Log
Essentia%.1f', $empire->essentia); $out .= sprintf('View Log
Essentia TypesFree: %.1f; Game: %.1f; Paid: %.1f
Add Essentia
Species%s
Home%s (%s)
Alliance'); if ( my $alliance = $empire->alliance ) { $out .= sprintf('%s (%s)', $alliance->id, $alliance->name, $alliance->id); } $out .= sprintf('
Invites Sent To'; my $invites_sent = Lacuna->db->resultset('Lacuna::DB::Result::Invite')->search({inviter_id => $empire->id}); $out .= join ' ; ', map { sprintf('%s (%s)', $_->id, $_->name, $_->id ) } map { $_->invitee } grep { $_->invitee_id } $invites_sent->all; $out .= '
Invite Accepted From'; my $invite_accepted = Lacuna->db->resultset('Lacuna::DB::Result::Invite')->search({invitee_id => $empire->id})->first; if ( $invite_accepted && $invite_accepted->inviter_id ) { my $inviter = $invite_accepted->inviter; $out .= sprintf('%s (%s)', $inviter->id, $inviter->name, $inviter->id); } $out .= '
Description%s
University Level%s
Isolationist%sToggle
Admin%s
Verified%sToggle
Mission Curator%sToggle
Admin Notes
Last set by: %s
Last set on: %s
View Log
'; return $self->wrap($out); } sub www_set_admin_notes { my ($self, $request) = @_; my $id = $request->param('id'); my $empire = Lacuna->db->empire($id); my $notes = decode_utf8($request->param('notes')); my $note = Lacuna->db->resultset('Log::EmpireAdminNotes')->new({ empire_id => $empire->id, empire_name => $empire->name, date_stamp => DateTime->now, notes => $notes, creator => $request->user, })->insert; return $self->www_view_empire($request); } sub www_view_admin_note_log { my ($self, $request, $id) = @_; $id ||= $request->param('id'); my $empire = Lacuna->db->empire($id); my $history = Lacuna->db->resultset('Log::EmpireAdminNotes')->search({empire_id => $empire->id},{order_by => { -desc => 'date_stamp' }}); my $out = sprintf '

"%s" Empire notes log

', $empire->name; $out .= sprintf('Back To Empire', $empire->id); $out .= ''; while (my $log = $history->next) { $out .= sprintf('', $log->date_stamp, $log->creator, Plack::Util::encode_html($log->notes)); } $out .= '
DateCreatorNotes
%s%s
%s
'; return $self->wrap($out); } sub www_set_alliance_logo { my ($self, $request, $id) = @_; $id ||= $request->param('id'); my $alliance = Lacuna->db->resultset('Lacuna::DB::Result::Alliance')->find($id); unless (defined $alliance) { confess [404, 'Alliance not found.']; } my $image = $request->param('logo_url'); unless (defined $image) { confess [404, 'Logo URL not supplied' ]; } my $out = ''; my $assets_url = Lacuna->config->get('assets_url'); my $full_url = $assets_url.'alliances/' . $image . '.png'; my $response = LWP::UserAgent->new->head($full_url); if ($response->is_success) { $alliance->image($image); $alliance->update; $out .= '

Success

'; $out .= sprintf('

Successfully updated %s to use %s

', $alliance->name, $full_url, $image); } else { $out .= '

Failure

'; $out .= sprintf('

Could not find an image for %s - has it been delivered yet?

', $image); } $out .= sprintf('

Back to %s

', $alliance->id, $alliance->name); return $self->wrap($out); } sub www_view_alliance { my ($self, $request, $id) = @_; $id ||= $request->param('id'); my $alliance = Lacuna->db->resultset('Lacuna::DB::Result::Alliance')->find($id); unless (defined $alliance) { confess [404, 'Alliance not found.']; } my $current_logo_path = $alliance->image; my $num = 0; if ($current_logo_path) { ($num) = $current_logo_path =~ /_(\d+)$/; $current_logo_path = qq["$current_logo_path"]; } else { $current_logo_path = "not set"; } my $uri = URI->new(Lacuna->config->get('server_url')); my ($domain) = $uri->authority =~ /^([^.]+)\./; my $new_logo_path = sprintf("%s/logo_%d_%03d", $domain, $alliance->id, $num + 1); my $leader = $alliance->leader; my $out = '

Manage Alliance

'; $out .= ''; $out .= ''; $out .= ''; $out .= sprintf('', $leader->id, $leader->id, $leader->name, $leader->home_planet_id, $leader->home_planet_id, $leader->last_login); for my $member( $alliance->members ) { next if $member->id == $leader->id; $out .= sprintf('', $member->id, $member->id, $member->name, $member->home_planet_id, $member->home_planet_id, $member->last_login); } $out .= '
IdNameHomeLast Login
%d%s%s%s
%d%s%s%s
'; return $self->wrap($out); } sub www_view_body { my ($self, $request, $id) = @_; $id ||= $request->param('id'); my $body = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->find($id); unless (defined $body) { confess [404, 'Body not found.']; } my $out = '

Manage Body

'; $out .= ''; $out .= sprintf('', $body->id); $out .= sprintf('', $body->class); $out .= sprintf('', $body->name); $out .= sprintf('', $body->zone, $body->zone); $out .= sprintf('', $body->x); $out .= sprintf('', $body->y); $out .= sprintf('', $body->orbit); $out .= sprintf('', $body->happiness, $body->id); $out .= sprintf('', $body->star_id, $body->star->name, $body->star_id, $body->star_id); if ($body->empire) { $out .= sprintf('', $body->empire_id, $body->empire->name, $body->empire_id); } else { $out .= sprintf(''); } $out .= '
Id%s
Class%s
Name%s
Zone%sBodies In This Zone
X%s
Y%s
Orbit%s
Happiness%s
Star%s (%s)Bodies Orbiting This Star
Empire%s (%s)
EmpireUnowned
'; return $self->wrap($out); } sub www_view_star { my ($self, $request, $id) = @_; $id ||= $request->param('id'); my $star = Lacuna->db->resultset('Lacuna::DB::Result::Map::Star')->find($id); unless (defined $star) { confess [404, 'Star not found.']; } my $out = '

Manage Star

'; $out .= ''; $out .= sprintf('', $star->id); $out .= sprintf('', $star->color); $out .= sprintf('', $star->name); $out .= sprintf('', $star->zone, $star->zone); $out .= sprintf('', $star->x); $out .= sprintf('', $star->y);#)) if ($star->station_id) { $out .= sprintf('', $star->station_id, $star->station->name, $star->station_id); } else { $out .= sprintf(''); } $out .= '
Id%s
Color%s
Name%s
Zone%sStars In This Zone
X%s
Y%s
Station%s (%s)
StationUnowned
'; return $self->wrap($out); } sub www_add_essentia { my ($self, $request) = @_; my $id = $request->param('id'); my $empire = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->find($id); unless (defined $empire) { confess [404, 'Empire not found.']; } $empire->add_essentia({ amount => $request->param('amount'), reason => $request->param('description'), type => 'free', }); $empire->update; return $self->www_view_empire($request, $id); } sub www_change_university_level { my ($self, $request) = @_; my $id = $request->param('id'); my $empire = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->find($id); unless (defined $empire) { confess [404, 'Empire not found.']; } $empire->university_level($request->param('university_level')); $empire->update; return $self->www_view_empire($request, $id); } sub www_add_happiness { my ($self, $request) = @_; my $id = $request->param('id'); my $body = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->find($id); unless (defined $body) { confess [404, 'Body not found.']; } $body->add_happiness($request->param('amount'))->update; return $self->www_view_body($request, $id); } sub www_view_logs { my ($self, $request) = @_; my $list = ' Request | Summary | Weekly Medals '; my $log = 'Choose a log file.'; my $log_dir = $ENV{LACUNA_LOG_DIR} || '/home/lacuna/server/log'; given ($request->param('file')) { when ('request') { $log = `tail -50 $log_dir/server/lacuna.log`; } when ('weekmedals') { $log = `tail -100 $log_dir/cron/weekly_medals.log`; } when ('summary') { $log = `tail -1000 $log_dir/cron/summarize_server.log`; } } my $file = "$log_dir/server/lacuna.log"; return $self->wrap($list.'
'.$log.'
'); } sub www_view_virality { my ($self, $request) = @_; my $out = '

Virality

'; my $dt_formatter = Lacuna->db->storage->datetime_parser; my (@accepts, @abandons, @creates, @invites, @dates, @deletes, @users, @stay, @vc, @gr, @cr, $previous, $max_viral, $max_change, $max_users, $max_stay); my $past30 = Lacuna->db->resultset('Lacuna::DB::Result::Log::Viral')->search({date_stamp => { '>=' => $dt_formatter->format_datetime(DateTime->now->subtract(days => 31))}}, { order_by => 'date_stamp'}); while (my $day = $past30->next) { unless (defined $previous) { $previous = $day; next; } push @dates, $day->date_stamp->month.'/'.$day->date_stamp->day; # users chart push @users, $day->total_users; $max_users = $users[-1] if ($max_users < $users[-1]); # stay chart push @stay, $day->active_duration / (60 * 60 * 24); $max_stay = $stay[-1] if ($max_stay < $stay[-1]); # viral chart push @vc, sprintf('%.0f', ($day->accepts / $previous->total_users) * 100); $max_viral = $vc[-1] if ($max_viral < $vc[-1]); push @gr, sprintf('%.0f', (($day->total_users - $previous->total_users) / $previous->total_users) * 100); $max_viral = $gr[-1] if ($max_viral < $gr[-1]); push @cr, sprintf('%.0f', ($day->deletes / $previous->total_users) * 100); $max_viral = $cr[-1] if ($max_viral < $cr[-1]); # change chart push @accepts, $day->accepts; $max_change = $accepts[-1] if ($max_change < $accepts[-1]); push @deletes, $day->deletes; $max_change = $deletes[-1] if ($max_change < $deletes[-1]); push @invites, $day->invites; $max_change = $invites[-1] if ($max_change < $invites[-1]); push @creates, $day->creates; $max_change = $creates[-1] if ($max_change < $creates[-1]); push @abandons, $day->abandons; $max_change = $abandons[-1] if ($max_change < $abandons[-1]); $previous = $day; } my $users_chart = 'http://chart.apis.google.com/chart?chxr=1,0,'.$max_users .'&chxt=x,y&chds=0,'.$max_users .'&chdl=Users&chf=bg,s,014986&chxs=0,ffffff|1,ffffff&chls=3&chxtc=1,-900&chs=900x300&cht=ls&chco=ffffff&chd=t:' .join(',', @users) .'&chxl=' .join('|', '0:', @dates); my $stay_chart = 'http://chart.apis.google.com/chart?chxr=1,0,'.$max_stay .'&chxt=x,y&chds=0,'.$max_stay.',0,'.$max_stay .'&chdl=Days|Deletes&chf=bg,s,014986&chxs=0,ffffff|1,ffffff&chls=3|3&chxtc=1,-900&chs=900x300&cht=ls&chco=ffffff,000000&chd=t:' .join('|', join(',', @stay), join(',', @deletes), ) .'&chxl=' .join('|', '0:', @dates); my $viral_chart = 'http://chart.apis.google.com/chart?chxr=1,0,'.$max_viral .'&chxt=x,y&chds=0,'.$max_viral.',0,'.$max_viral.',0,'.$max_viral .'&chdl=Viral%20Coefficient|Growth%20Rate|Churn%20Rate&chf=bg,s,014986&chxs=0,ffffff|1,ffffff&chls=3|3|3&chxtc=1,-900&chs=900x300&cht=ls&chco=00ff00,ffb400,b400ff&chd=t:' .join('|', join(',', @vc), join(',', @gr), join(',', @cr), ) .'&chxl=' .join('|', '0:', @dates); my $change_chart = 'http://chart.apis.google.com/chart?chxr=1,0,'.$max_change .'&chxt=x,y&chds=0,'.$max_change.',0,'.$max_change.',0,'.$max_change.',0,'.$max_change.',0,'.$max_change .'&chdl=Invites|Accepts|Creates|Deletes|Abandons&chf=bg,s,014986&chxs=0,ffffff|1,ffffff&chls=3|3|3|3|3&chxtc=1,-900&chs=900x300&cht=ls&chco=ff8888,88ff88,8888ff,ff88ff,000000&chd=t:' .join('|', join(',', @invites), join(',', @accepts), join(',', @creates), join(',', @deletes), join(',', @abandons), ) .'&chxl=' .join('|', '0:', @dates); my $avg_vc = sprintf('%.2f', sum(@vc) / 100 / scalar(@vc)); my $avg_gr = sprintf('%.2f', sum(@gr) / 100 / scalar(@gr)); my $avg_cr = sprintf('%.2f', sum(@cr) / 100 / scalar(@cr)); $out .= '
Viral Coefficient
'.$avg_vc.'
Growth Rate
'.$avg_gr.'
Churn Rate
'.$avg_cr.'
viral chart

Change

change chart

Total Users

users chart

Stay

users chart
'; return $self->wrap($out); } sub www_view_economy { my ($self, $request) = @_; my $out = '

Economy

'; my $dt_formatter = Lacuna->db->storage->datetime_parser; my (@dates, $previous, @arpu, $max_purchases, @p30, @p100, @p200, @p600, @p1300, $max_revenue, @revenue, @r30, @r100, @r200, @r600, @r1300); my ($max_out, @out_boost, @out_mission, @out_recycle, @out_ship, @out_spy, @out_glyph, @out_party, @out_building, @out_trade, @out_delete, @out_other); my ($max_in, @in_mission, @in_purchase, @in_trade, @in_redemption, @in_vein, @in_vote, @in_tutorial, @in_other); my $past30 = Lacuna->db->resultset('Lacuna::DB::Result::Log::Economy')->search({date_stamp => { '>=' => $dt_formatter->format_datetime(DateTime->now->subtract(days => 31))}}, { order_by => 'date_stamp'}); while (my $day = $past30->next) { unless (defined $previous) { $previous = $day; next; } push @dates, $day->date_stamp->month.'/'.$day->date_stamp->day; # average revenue per user if ($day->total_users) { push @arpu, (( ($day->purchases_30 * 3) + ($day->purchases_100 * 6) + ($day->purchases_200 * 10) + ($day->purchases_600 * 25) + ($day->purchases_1300 + 50) ) / $day->total_users); } else { push @arpu, 0; } # purchases chart push @p30, $day->purchases_30; my $sum_purchases = $day->purchases_30; push @p100, $day->purchases_100; $sum_purchases += $day->purchases_100; push @p200, $day->purchases_200; $sum_purchases += $day->purchases_200; push @p600, $day->purchases_600; $sum_purchases += $day->purchases_600; push @p1300, $day->purchases_1300; $sum_purchases += $day->purchases_1300; $max_purchases = $sum_purchases if ($max_purchases < $sum_purchases); # revenue chart push @r30, $day->purchases_30 * 3; my $sum_revenue = $day->purchases_30 *3; push @r100, $day->purchases_100 * 6; $sum_revenue += $day->purchases_100 *6; push @r200, $day->purchases_200 * 10; $sum_revenue += $day->purchases_200 * 10; push @r600, $day->purchases_600 * 25; $sum_revenue += $day->purchases_600 * 25; push @r1300, $day->purchases_1300 * 50; $sum_revenue += $day->purchases_1300 * 50; push @revenue, $sum_revenue; $max_revenue = $sum_revenue if ($max_revenue < $sum_revenue); # in chart push @in_purchase, $day->in_purchase; my $sum_in = $in_purchase[-1]; push @in_trade, $day->in_trade; $sum_in += $in_trade[-1]; push @in_redemption, $day->in_redemption; $sum_in += $in_redemption[-1]; push @in_vein, $day->in_vein; $sum_in += $in_vein[-1]; push @in_vote, $day->in_vote; $sum_in += $in_vote[-1]; push @in_tutorial, $day->in_tutorial; $sum_in += $in_tutorial[-1]; push @in_mission, $day->in_mission; $sum_in += $in_mission[-1]; push @in_other, $day->in_other; $sum_in += $in_other[-1]; $max_in = $sum_in if ($max_in < $sum_in); # out chart push @out_boost, $day->out_boost; my $sum_out = $out_boost[-1]; push @out_recycle, $day->out_recycle; $sum_out += $out_recycle[-1]; push @out_ship, $day->out_ship; $sum_out += $out_ship[-1]; push @out_spy, $day->out_spy; $sum_out += $out_spy[-1]; push @out_glyph, $day->out_glyph; $sum_out += $out_glyph[-1]; push @out_party, $day->out_party; $sum_out += $out_party[-1]; push @out_building, $day->out_building; $sum_out += $out_building[-1]; push @out_trade, $day->out_trade; $sum_out += $out_trade[-1]; push @out_delete, $day->out_delete; $sum_out += $out_delete[-1]; push @out_mission, $day->out_mission; $sum_out += $out_mission[-1]; push @out_other, $day->out_other; $sum_out += $out_other[-1]; $max_out = $sum_out if ($max_out < $sum_out); } my $in_chart = 'http://chart.apis.google.com/chart?chxr=1,0,'.$max_in .'&chxt=x,y&chds=0,'.$max_in.',0,'.$max_in.',0,'.$max_in.',0,'.$max_in.',0,'.$max_in.',0,'.$max_in.',0,'.$max_in.',0,'.$max_in .'&chdl=Purchased|Trade|Redemption|Vein|Vote|Tutorial|Mission|Other&chf=bg,s,014986&chxs=0,ffffff|1,ffffff&chls=3|3|3|3|3|3|3|3&chxtc=1,-900&chs=900x300' .'&cht=bvs&chco=00b4ff,00ff00,009900,ffff00,ff7700,b400ff,ffaaff,ff0000&chd=t:' .join('|', join(',', @in_purchase), join(',', @in_trade), join(',', @in_redemption), join(',', @in_vein), join(',', @in_vote), join(',', @in_tutorial), join(',', @in_mission), join(',', @in_other), ) .'&chxl=' .join('|', '0:', @dates); my $out_chart = 'http://chart.apis.google.com/chart?chxr=1,0,'.$max_out .'&chxt=x,y&chds=0,'.$max_out.',0,'.$max_out.',0,'.$max_out.',0,'.$max_out.',0,'.$max_out.',0,'.$max_out.',0,'.$max_out.',0,'.$max_out.',0,'.$max_out.',0,'.$max_out.',0,'.$max_out .'&chdl=Boosts|Recyling|Ships|Spies|Glyphs|Parties|Construction|Trade|Mission|Delete|Other&chf=bg,s,014986&chxs=0,ffffff|1,ffffff&chls=3|3|3|3|3|3|3|3|3|3|3&chxtc=1,-900&chs=900x300' .'&cht=bvs&chco=00b4ff,00ff00,009900,ffff00,ff7700,ff0000,ffaaff,b400ff,ffffff,999999,000000&chd=t:' .join('|', join(',', @out_boost), join(',', @out_recycle), join(',', @out_ship), join(',', @out_spy), join(',', @out_glyph), join(',', @out_party), join(',', @out_building), join(',', @out_trade), join(',', @out_mission), join(',', @out_delete), join(',', @out_other), ) .'&chxl=' .join('|', '0:', @dates); my $revenue_chart = 'http://chart.apis.google.com/chart?chxr=1,0,'.$max_revenue .'&chxt=x,y&chds=0,'.$max_revenue.',0,'.$max_revenue.',0,'.$max_revenue.',0,'.$max_revenue.',0,'.$max_revenue .'&chdl=$3+(30)|$6+(100)|$10+(200)|$25+(600)|$50+(1300)&chf=bg,s,014986&chxs=0,ffffff|1,ffffff&chls=3|3|3|3|3' .'&chxtc=1,-900&chs=900x300&cht=bvs&chco=00ff00,ffb400,b400ff,00b4ff,ff0000&chd=t:' .join('|', join(',', @r30), join(',', @r100), join(',', @r200), join(',', @r600), join(',', @r1300), ) .'&chxl=' .join('|', '0:', @dates); my $purchases_chart = 'http://chart.apis.google.com/chart?chxr=1,0,'.$max_purchases .'&chxt=x,y&chds=0,'.$max_purchases.',0,'.$max_purchases.',0,'.$max_purchases.',0,'.$max_purchases.',0,'.$max_purchases .'&chdl=$3+(30)|$6+(100)|$10+(200)|$25+(600)|$50+(1300)&chf=bg,s,014986&chxs=0,ffffff|1,ffffff&chls=3|3|3|3|3&chxtc=1,-900&chs=900x300&cht=bvs&chco=00ff00,ffb400,b400ff,00b4ff,ff0000&chd=t:' .join('|', join(',', @p30), join(',', @p100), join(',', @p200), join(',', @p600), join(',', @p1300), ) .'&chxl=' .join('|', '0:', @dates); my $arpu_chart = 'http://chart.apis.google.com/chart?chxr=1,0,1' .'&chxt=x,y&chds=0,1' .'&chdl=Dollars&chf=bg,s,014986&chxs=0,ffffff|1,ffffff&chls=3&chxtc=1,-900&chs=900x300&cht=ls&chco=ffffff&chd=t:' .join(',', @arpu) .'&chxl=' .join('|', '0:', @dates); $out .= '

Revenue

revenue chart

User Purchases

purchases chart

Average Revenue Per User

arpu chart

Essentia Spent

out chart

Essentia Earned

in chart
'; return $self->wrap($out); } sub www_default { my ($self, $request) = @_; my $announcement = Lacuna->cache->get('announcement','message'); $announcement =~ s/\>/>/xmsg; $announcement =~ s/\wrap('

Lacuna Expanse Admin Console

Server Version: '.Lacuna->version.'
Announcement

Announcements last for 24 hours. HTML head and body are provided, you just need to type the content. Make sure links target "_new".

Delete this announcement.
Server Utilities
'); } sub www_change_announcement { my ($self, $request) = @_; my $cache = Lacuna->cache; $cache->set('announcement','alert', create_uuid_as_string(UUID_V4), 60*60*24); $cache->set('announcement','message', decode_utf8($request->param('message')), 60*60*24); return $self->wrap('Announcement saved.'); } sub www_delete_announcement { my ($self, $request) = @_; my $cache = Lacuna->cache; $cache->delete('announcement','alert'); $cache->delete('announcement','message'); return $self->wrap('Announcement deleted.'); } sub www_server_wide_recalc { my ($self, $request) = @_; Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->search({empire_id => {'>', 0}})->update({needs_recalc=>1}); return $self->wrap('Done!'); } sub www_delambert { my ($self, $request) = @_; my ($scratch) = Lacuna->db->resultset('Lacuna::DB::Result::AIScratchPad')->search({ai_empire_id => -9, body_id => 0}); my $scratchpad = $scratch->pad; if ($request->param('submit')) { $scratchpad->{status} = lc $request->param('status') eq 'war' ? 'war' : 'peace'; $scratchpad->{buy_max_price_per_plan} = $request->param('buy_max_price_per_plan'); $scratchpad->{buy_trades_probability} = $request->param('buy_trades_probability'); $scratchpad->{sell_glyph_probability} = $request->param('sell_glyph_probability'); $scratchpad->{sell_glyph_type} = $request->param('sell_glyph_type'); $scratchpad->{sell_glyph_min_e} = $request->param('sell_glyph_min_e'); $scratchpad->{sell_glyph_max_e} = $request->param('sell_glyph_max_e'); $scratchpad->{sell_glyph_max_batch} = $request->param('sell_glyph_max_batch'); $scratchpad->{sell_plan_probability} = $request->param('sell_plan_probability'); $scratchpad->{sell_plan_min_level} = $request->param('sell_plan_min_level'); $scratchpad->{sell_plan_max_level} = $request->param('sell_plan_max_level'); $scratchpad->{sell_plan_max_batch} = $request->param('sell_plan_max_batch'); $scratchpad->{sell_plan_min_hall_factor} = $request->param('sell_plan_min_hall_factor'); $scratchpad->{sell_plan_max_hall_factor} = $request->param('sell_plan_max_hall_factor'); $scratchpad->{sell_max_glyph_trades_in_zone} = $request->param('sell_max_glyph_trades_in_zone'); $scratchpad->{sell_max_plan_trades_in_zone} = $request->param('sell_max_plan_trades_in_zone'); $scratch->pad($scratchpad); $scratch->update; } my $out = ''; my $bodies = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->search({ empire_id => -9, }, { order_by => ['name'], }); $out .= '

DeLamberti

'; $out .= '
'; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= '
Status
Max Plan Buy Price
Probability of Colony Buying each hour (100=100%)
Probability of Colony selling glyphs each hour (%)
Minimum selling price per glyph
Maximum selling price per glyph
Maximum number of glyphs to batch in sale
Glyphs to sell, comma separate
Probability of Colony selling plans each hour (%)
Minimum plan level to sell
Maximum plan level to sell
Maximum number of plans to batch is sale
Minimum Hall equivalent costing factor
Maximum Hall equivalent costing factor
Maximum sell glyph trades in any one zone
Maximum sell plan trades in any one zone
 
'; $out .= '

War Status

'; $out .= '

DeLamberti Colonies

'; $out .= ''; while (my $body = $bodies->next) { $out .= sprintf('', $body->id, $body->id, $body->name, $body->x, $body->y, $body->zone); } $out .= '
IdNameXYZone
%s%s%s%s%s
'; return $self->wrap($out); } sub www_delambert_war { my ($self, $request) = @_; my ($scratch) = Lacuna->db->resultset('Lacuna::DB::Result::AIScratchPad')->search({ai_empire_id => -9, body_id => 0}); my $scratchpad = $scratch->pad; if ($request->param('submit')) { $scratchpad->{attack}{$request->param('attacker_id')} = { sweepers => $request->param('sweepers'), scows => $request->param('scows'), snarks => $request->param('snarks'), colony_id => $request->param('colony_id'), frequency => $request->param('frequency'), }; $scratch->pad($scratchpad); $scratch->update; } my $out = ''; $out .= "

DeLamberti war status

\n"; my @ai_defence = Lacuna->db->resultset('Lacuna::DB::Result::AIBattleSummary')->search({ defending_empire_id => -9, }); my @ai_attack = Lacuna->db->resultset('Lacuna::DB::Result::AIBattleSummary')->search({ attacking_empire_id => -9, }); # If the AI is attacked, we don't care who won or lost, just that there was an action against the AI my %defence = map { $_->attacking_empire_id => { attack_victories => $_->attack_victories, defense_victories => $_->defense_victories, attack_spy_hours => $_->attack_spy_hours, weight => $_->attack_victories + $_->defense_victories + $_->attack_spy_hours * 2, } } @ai_defence; # If the AI attacks, we just care about when the AI wins the attack my %attack = map { $_->defending_empire_id => { attack_victories => $_->attack_victories, defense_victories => $_->defense_victories, attack_spy_hours => $_->attack_spy_hours, weight => ($_->attack_victories / 2) + $_->attack_spy_hours, } } @ai_attack; # Sort the attackers so that those who have done the most un-retaliated damage are shown first my @worst_attackers = sort {( $defence{$a}{weight} - defined $attack{$a} ? $attack{$a}{weight} : 0) <=> ( $defence{$b}{weight} - defined $attack{$b} ? $attack{$b}{weight} : 0 ) } keys %defence; $out .= "\n"; ATTACKER: foreach my $attacker (@worst_attackers) { my $attack_empire = Lacuna->db->resultset('Lacuna::DB::Result::Empire')->find($attacker); next ATTACKER unless $attack_empire; # Obtain all colonies of the attacking empire, sorted by population desc. my @colonies = Lacuna->db->resultset('Lacuna::DB::Result::Map::Body')->search({ empire_id => $attacker, }); @colonies = sort {$b->population <=> $a->population} @colonies; if (not defined $scratchpad->{attack}{$attacker}) { $scratchpad->{attack}{$attacker} = { colony_id => $colonies[0]->id, sweepers => 1000, snarks => 200, scows => 200, frequency => 'Once', }; $scratch->pad($scratchpad); $scratch->update; } my $sweepers = $scratchpad->{attack}{$attacker}{sweepers}; my $snarks = $scratchpad->{attack}{$attacker}{snarks}; my $scows = $scratchpad->{attack}{$attacker}{scows}; my $frequency = $scratchpad->{attack}{$attacker}{frequency}; my $counter = {attack_victories=>0, defense_victories=>0, attack_spy_hours=>0, weight=>0}; if (defined $attack{$attacker}) { $counter = { attack_victories => $attack{$attacker}{attack_victories}, defense_victories => $attack{$attacker}{defense_victories}, attack_spy_hours => $attack{$attacker}{attack_spy_hours}, weight => $attack{$attacker}{weight}, }; } $out .= ""; $out .= ""; $out .= ""; $out .= ""; $out .= ""; $out .= ""; $out .= ""; $out .= ""; $out .= ""; $out .= ""; $out .= ""; $out .= ""; } $out .= "
AttackerA-VictoriesA-DefeatsA-Spy HoursAttack WeightR-VictoriesR-DefeatsR-Spy HoursRetaliate WeightColonyFrequencyAttack SweepersAttack ScowsAttack SnarkAction
".$attack_empire->name."".$defence{$attacker}{attack_victories}."".$defence{$attacker}{defense_victories}."".$defence{$attacker}{attack_spy_hours}."".$defence{$attacker}{weight}."".$counter->{attack_victories}."".$counter->{defense_victories}."".$counter->{attack_spy_hours}."".$counter->{weight}."
\n"; $out .= "\n"; return $self->wrap($out); } #" sub wrap { my ($self, $content) = @_; my $uri = URI->new(Lacuna->config->get('server_url')); my ($domain) = $uri->authority =~ /^([^.]+)\./; return $self->wrapper('
'. $content .'
', { title => "Admin Console ($domain)"} ); } no Moose; __PACKAGE__->meta->make_immutable(inline_constructor => 0);