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('Id Code Amount Description Date Created Used ', $code, $toggle_used );
while (my $code = $codes->next) {
$out .= sprintf('%s %s %s %s %s %s ', $code->id, $code->code, $code->amount, $code->description, $code->date_created, $code->used);
}
$out .= '';
$out .= '
';
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 .= 'Date Amount Description From ID From Transaction ID ';
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('%s %s %s %s %s %s ',
$transaction->date_stamp, $transaction->amount, $transaction->description,
$empire_link, $transaction->from_name, $transaction->transaction_id);
}
$out .= '
';
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 .= 'ID Empire Name Log-in Date Log-out Date Extended IP Address Sitter API Key ';
while (my $login = $logins->next) {
my $sitter = $login->is_sitter ? 'Sitter' : '';
$out .= sprintf('%d ',
$login->empire_id, $login->empire_id);
$out .= sprintf('%s %s %s %s ',
$login->empire_name, $login->date_stamp, $login->log_out_date, $login->extended );
$out .= sprintf('%s ',
$login->ip_address, $login->ip_address );
$out .= sprintf('%s ', $sitter);
$out .= sprintf('%s ',
$login->api_key, $login->api_key );
$out .= ' ';
}
$out .= '
';
$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 .= 'Date New Name Old Name ';
while (my $log = $history->next) {
$out .= sprintf('%s %s %s ',
$log->date_stamp, $log->empire_name, $log->old_empire_name);
}
$out .= '
';
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 .= 'ID Empire Name Email Created Last Login ';
while (my $match = $search->next) {
$out .= sprintf('%d ',
$match->id, $match->id);
$out .= sprintf('%s %s %s %s ',
$match->name, $match->email, $match->date_created, $match->last_login );
}
$out .= '
';
$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 .= sprintf('Id ⇓ ⇑ ', $name, $name );
$out .= sprintf('Name ⇓ ⇑ ', $name, $name );
$out .= 'Species Home ';
$out .= sprintf('Last Login ⇓ ⇑ ', $name, $name );
while (my $empire = $empires->next) {
$out .= sprintf('%s %s %s %s %s ', $empire->id, $empire->id, $empire->name, $empire->species_name, $empire->home_planet_id, $empire->home_planet_id, $empire->last_login);
}
$out .= '
';
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 .= 'Id Name X Y Zone Star O Type Happiness Empire ';
while (my $body = $bodies->next) {
$out .= sprintf('%s %s %s %s %s %s (%d) %s %s %s %s ',
$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 .= '
';
$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 .= 'Id Name X Y Zone Station ';
while (my $star = $stars->next) {
$out .= sprintf('%s %s %s %s %s %s ',
$star->id, $star->id, $star->name, $star->x, $star->y, $star->zone,
$star->station_id || '', $star->station_id || '');
}
$out .= '
';
$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 .= 'Id Name X Y Level InProgress Efficiency ';
while (my $building = $buildings->next) {
$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 .= '';
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 .= 'Id Name Type Stealth Hold Size Speed Combat Task Delete ';
while (my $ship = $ships->next) {
$out .= sprintf('%s %s %s %s %s %s %s ', $ship->id, $ship->name, $ship->type_formatted, $ship->stealth, $ship->hold_size, $ship->speed, $ship->combat);
if ($ship->task eq 'Travelling') {
$out .= sprintf('%s ', $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('%s %s (%d, %d) ', $ship->task, $target->name, $target->x, $target->y, $ship->id, $body_id);
}
elsif ($ship->task ne 'Docked') {
$out .= sprintf('%s ', $ship->task, $ship->id, $body_id);
}
else {
$out .= sprintf('%s ', $ship->task);
}
$out .= sprintf(' ', $ship->id, $body_id);
}
$out .= '
';
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 .= '';
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 .= '';
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 .= '';
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('Self Destruct Active! Expires: %s ', $empire->self_destruct_date);
}
$out .= sprintf('Id %s ', $empire->id);
$out .= sprintf('RPC Requests %s / %s ', $empire->rpc_count || 0, $empire->rpc_limit);
$out .= sprintf('Name %s ', $empire->name);
$out .= sprintf('View History ',$empire->id);
$out .= sprintf(' | Find Similar Empire Names ',$empire->id);
$out .= sprintf('Email %s ', $empire->email);
if ( $empire->email ) {
$out .= sprintf('Find Similar Email Usernames ',$empire->id);
$out .= sprintf(' | Find Same Email Domains ',$empire->id);
}
$out .= ' ';
$out .= sprintf('Created %s ', $empire->date_created);
$out .= sprintf('Stage %s ', $empire->stage);
$out .= sprintf('Last Login %s ', $empire->last_login);
$out .= sprintf('View Log ',$empire->id);
$out .= sprintf('Essentia %.1f ', $empire->essentia);
$out .= sprintf('View Log ',$empire->id);
$out .= sprintf('Essentia Types Free: %.1f; Game: %.1f; Paid: %.1f ', $empire->essentia_free, $empire->essentia_game, $empire->essentia_paid);
$out .= sprintf('Add Essentia
', $empire->id);
$out .= sprintf('Species %s ', $empire->species_name);
$out .= sprintf('Home %s (%s) ', $empire->home_planet_id, $empire->home_planet->name, $empire->home_planet_id);
$out .= sprintf('Alliance ');
if ( my $alliance = $empire->alliance ) {
$out .= sprintf('%s (%s)', $alliance->id, $alliance->name, $alliance->id);
}
$out .= sprintf(' ');
$out .= '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 .= ' ';
$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 .= ' ';
$out .= sprintf('Description %s ', $empire->description);
$out .= sprintf('University Level %s ', $empire->university_level, $empire->id);
$out .= sprintf('Isolationist %s Toggle ', $empire->is_isolationist, $empire->id);
$out .= sprintf('Admin %s ', $empire->is_admin);
$out .= sprintf('Verified %s Toggle ', $empire->is_verified, $empire->id);
$out .= sprintf('Mission Curator %s Toggle ', $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('Admin Notes %s Last set by: %s Last set on: %sView Log ',
$empire->id,
$notes ? $notes->notes : '',
$notes ? $notes->creator : 'not set yet ',
$notes ? $notes->date_stamp : 'not set yet ',
$empire->id
);
$out .= '
';
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 .= 'Date Creator Notes ';
while (my $log = $history->next) {
$out .= sprintf('%s %s %s ',
$log->date_stamp, $log->creator, Plack::Util::encode_html($log->notes));
}
$out .= '
';
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 .= 'Id Name Home Last Login ';
$out .= sprintf('%d %s %s %s ',
$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('%d %s %s %s ',
$member->id, $member->id, $member->name, $member->home_planet_id, $member->home_planet_id, $member->last_login);
}
$out .= '
';
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('Id %s ', $body->id);
$out .= sprintf('Class %s ', $body->class);
$out .= sprintf('Name %s ', $body->name);
$out .= sprintf('Zone %s Bodies In This Zone ', $body->zone, $body->zone);
$out .= sprintf('X %s ', $body->x);
$out .= sprintf('Y %s ', $body->y);
$out .= sprintf('Orbit %s ', $body->orbit);
$out .= sprintf('Happiness %s ', $body->happiness, $body->id);
$out .= sprintf('Star %s (%s)Bodies Orbiting This Star ', $body->star_id, $body->star->name, $body->star_id, $body->star_id);
if ($body->empire) {
$out .= sprintf('Empire %s (%s) ', $body->empire_id, $body->empire->name, $body->empire_id);
}
else {
$out .= sprintf('Empire Unowned ');
}
$out .= '
';
$out .= sprintf('View Resources ', $body->id);
$out .= sprintf('View Buildings ', $body->id);
$out .= sprintf('View Ships ', $body->id);
$out .= sprintf('View Plans ', $body->id);
$out .= sprintf('View Glyphs ', $body->id);
$out .= sprintf('Recalculate Body Stats ', $body->id);
$out .= sprintf('Complete All Builds ', $body->id);
$out .= sprintf('Send Stellar Flare ', $body->id);
$out .= sprintf('Send Meteor Shower ', $body->id);
$out .= sprintf('Send Pestilence ', $body->id);
$out .= ' ';
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('Id %s ', $star->id);
$out .= sprintf('Color %s ', $star->color);
$out .= sprintf('Name %s ', $star->name);
$out .= sprintf('Zone %s Stars In This Zone ', $star->zone, $star->zone);
$out .= sprintf('X %s ', $star->x);
$out .= sprintf('Y %s ', $star->y);#))
if ($star->station_id) {
$out .= sprintf('Station %s (%s) ', $star->station_id, $star->station->name, $star->station_id);
}
else {
$out .= sprintf('Station Unowned ');
}
$out .= '
';
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.'
Change
Total Users
Stay
';
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
User Purchases
Average Revenue Per User
Essentia Spent
Essentia Earned
';
return $self->wrap($out);
}
sub www_default {
my ($self, $request) = @_;
my $announcement = Lacuna->cache->get('announcement','message');
$announcement =~ s/\>/>/xmsg;
$announcement =~ s/\</xmsg;
return $self->wrap('Lacuna Expanse Admin Console
Server Version: '.Lacuna->version.'
Announcement
'.$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 .= 'War Status
';
$out .= 'DeLamberti Colonies ';
$out .= 'Id Name X Y Zone ';
while (my $body = $bodies->next) {
$out .= sprintf('%s %s %s %s %s ', $body->id, $body->id, $body->name, $body->x, $body->y, $body->zone);
}
$out .= '
';
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 .= "Attacker A-Victories A-Defeats A-Spy Hours Attack Weight R-Victories R-Defeats R-Spy Hours Retaliate Weight Colony Frequency Attack Sweepers Attack Scows Attack Snark Action \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 .= "".$attack_empire->name." ".$defence{$attacker}{attack_victories}." ".$defence{$attacker}{defense_victories}." ";
$out .= "".$defence{$attacker}{attack_spy_hours}." ".$defence{$attacker}{weight}." ";
$out .= "".$counter->{attack_victories}." ".$counter->{defense_victories}." ";
$out .= "".$counter->{attack_spy_hours}." ".$counter->{weight}." ";
$out .= "";
$out .= "";
foreach my $colony (@colonies) {
my $selected = ' selected ' if $colony->id == $scratchpad->{attack}{$attacker}{colony_id};
$out .= "".$colony->name." ";
}
$out .= " ";
$out .= "";
foreach my $freq (qw(never once hourly daily)) {
my $selected = ' selected ' if $scratchpad->{attack}{$attacker}{frequency} eq $freq;
$out .= "$freq ";
}
$out .= " ";
$out .= " ";
$out .= " ";
$out .= " ";
$out .= " ";
$out .= " ";
}
$out .= "
\n";
$out .= "\n";
$out .= "A-Victories, A-Defeats and A-Spy hours are attacks against the DeLamberti ";
$out .= "R-Victories, R-Defeats and R-Spy hours are retaliations by the DeLamberti ";
$out .= "Attack Weight, is a measure of the amount of attacks against the AI ";
$out .= "Retaliate Weight, is a measure of the AI Retaliation against those attacks ";
$out .= "The list is sorted so that those empires with the highest (Attack Weight - Retaliate Weight) are first ";
$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('
',
{ title => "Admin Console ($domain)"}
);
}
no Moose;
__PACKAGE__->meta->make_immutable(inline_constructor => 0);