#!/usr/bin/perl use strict; #use warnings; # summary prints many uninitialized use use Date::Parse; use Date::Format; use Data::Dumper qw(Dumper); my $debug = 0; my $me = "Asmadeus"; my $interbattle_time = 15; my $eq_copypaste_limit = 430; my $eq_copypaste_prefix = '/9 '; my ($time, $rawtime, $line); my ($player, $dmg); my ($time_start, $time_end); my %players = (); my %pets = ( 'Petpower' => 'Novethir', 'Seggl' => 'Waldmaus', 'Shadow' => 'Darkform', 'Tinkerbelle' => 'Helvetinker', 'Eddepet' => 'Slet', 'Slacker' => 'Ggagar' ); sub reset_stats { %players = (); $time_start = 0; return; } sub number_scale { my ($num) = @_; # I find it prettier to leave giga out and not have giga for now if ($num > 1000 ** 2) { return sprintf "%.2fm", $num / (1000 ** 2); } if ($num > 1000) { return sprintf "%.2fk", $num / 1000; } return sprintf "%d ", $num } sub print_forum { our ($total_damage, $mainmob, $duration); my $i = 1; print "FORUM ----------------------\n\n"; printf "[b]Total %s (and possibly friends)[/b]\n", $mainmob; printf "[b]--- DMG:[/b] %s @ %sdps (%ds)\n\n", number_scale($total_damage), number_scale($total_damage/$duration), $duration; foreach my $player (sort { $players{$b}{total} <=> $players{$a}{total} } keys %players) { next if $players{$player}{mob}; next unless $players{$player}{total}; my $player_duration = $players{$player}{time_end} - $players{$player}{time_start} + 1; printf "[b]#%d %s[/b]\n", $i++, $player; printf "[b]--- DMG:[/b] %s @ %sdps (%ds)\n", number_scale($players{$player}{total}), number_scale($players{$player}{total} / $player_duration), $player_duration; foreach my $type (sort { $players{$player}{$b} <=> $players{$player}{$a} } ('melee', 'dd', 'dot')) { printf "[b]---- %s (%d%%):[/b] %s (crits: %s, %d%% damages, %d%% hits)\n", $type, $players{$player}{$type} * 100 / $players{$player}{total}, number_scale( $players{$player}{$type}), number_scale($players{$player}{"${type}_crit"}), $players{$player}{"${type}_crit"} * 100 / $players{$player}{$type}, $players{$player}{"${type}_crit_count"} * 100 / $players{$player}{"${type}_count"} if $players{$player}{$type}; } printf "[b]---- cast count:[/b] %d\n", $players{$player}{castcount} if $players{$player}{castcount}; print "\n"; } } sub print_selfdetails { print "\nSelf nuke summary\n"; foreach my $spell (sort { $players{$me}{dds}{$b} <=> $players{$me}{dds}{$a} } keys %{$players{$me}{dds_count}}) { printf "%s: %d / %d = %d\n", $spell, $players{$me}{dds}{$spell}, $players{$me}{dds_count}{$spell}, $players{$me}{dds}{$spell} / $players{$me}{dds_count}{$spell}; } print "\nSelf dot summary\n"; foreach my $spell (sort { $players{$me}{dots}{$b} <=> $players{$me}{dots}{$a} } keys %{$players{$me}{dots_count}}) { printf "%s: %d / %d = %d\n", $spell, $players{$me}{dots}{$spell}, $players{$me}{dots_count}{$spell}, $players{$me}{dots}{$spell} / $players{$me}{dots_count}{$spell}; } print "\n"; } sub print_summary { our $duration = $time_end - $time_start + 1; our $total_damage = 0; our $mainmob = 'fight'; printf "Duration: %d (%s - %s)\n", $duration, time2str("%Y-%m-%d %T", $time_start), time2str("%T", $time_end); foreach my $player (sort { $players{$b}{damaged} <=> $players{$a}{damaged} } keys %players) { next unless $players{$player}{mob}; printf "%s (%s @%sdps in %d)\n", $player, number_scale($players{$player}{damaged}), number_scale($players{$player}{damaged} / $duration), $duration; $mainmob = $player if $mainmob eq 'fight'; } foreach my $player (keys %players) { next if $players{$player}{mob}; $total_damage += $players{$player}{total}; } print "\nEQ copypaste string\n"; my ($eqstring, $len, $i) = ($eq_copypaste_prefix, length $eq_copypaste_prefix, 1); $eqstring .= sprintf "%s in %ds, ", $mainmob, $duration; $eqstring .= sprintf "%s @%sdps --- ", number_scale($total_damage), number_scale($total_damage / $duration); foreach my $player (sort { $players{$b}{total} <=> $players{$a}{total} } keys %players) { next if $players{$player}{mob}; next unless $players{$player}{total}; my $player_duration = $players{$player}{time_end} - $players{$player}{time_start} + 1; $_ = sprintf "#%d %s %s@(%sdps in %ds)", $i++, $player, number_scale($players{$player}{total}), number_scale($players{$player}{total} / $player_duration), $player_duration; $len += length; if ($len > $eq_copypaste_limit) { print $eqstring, "\n"; $eqstring = $eq_copypaste_prefix; $len = length $eqstring + length; } else { $eqstring .= ", " unless $i == 2; } $eqstring .= $_; } print $eqstring, "\n"; print_selfdetails; print_forum; print "\n"; print Dumper \%players{$me} if $debug; return; } sub add_dd { my ($mob, $caster, $dmg) = @_; $player = check_pet($caster); $mob = normalize_mob($mob); if ($player eq $me and $mob ne $me) { $players{$mob}{mob} = 1; } $players{$player}{total} += $dmg; $players{$mob}{damaged} += $dmg; $players{$player}{count}++; $players{$player}{dd} += $dmg; $players{$player}{dd_count}++; $players{$player}{time_start} = $time unless $players{$player}{time_start}; $players{$player}{time_end} = $time; if ($players{$player}{$player}{lastspell}) { $players{$player}{dds_count}{$players{$player}{$caster}{lastspell}}++ if $players{$player}{$caster}{lastspell} and !$players{$player}{$caster}{nukecounted}; $players{$player}{$caster}{nukecounted} = 1; $players{$player}{dds}{$players{$player}{$caster}{lastspell}} += $dmg; } if ($line =~ /\(Critical\)/) { $players{$player}{dd_crit} += $dmg; $players{$player}{dd_crit_count}++; } $time_start = $time unless $time_start; $time_end = $time; } sub add_dot { my ($mob, $caster, $dmg, $dot) = @_; $player = check_pet($caster); $mob = normalize_mob($mob); $players{$player}{total} += $dmg; $players{$mob}{damaged} += $dmg; $players{$player}{count}++; $players{$player}{dots}{$dot} += $dmg; $players{$player}{dots_count}{$dot}++; $players{$player}{dot} += $dmg; $players{$player}{dot_count}++; $players{$player}{time_start} = $time unless $players{$player}{time_start}; $players{$player}{time_end} = $time; if ($line =~ /\(Critical\)/) { $players{$player}{dot_crit} += $dmg; $players{$player}{dot_crit_count}++; } $time_start = $time unless $time_start; $time_end = $time; } sub add_melee { my ($mob, $hitter, $dmg, $type) = @_; $player = check_pet($hitter); $mob = normalize_mob($mob); $players{$player}{total} += $dmg; $players{$mob}{damaged} += $dmg; $players{$player}{count}++; $players{$player}{melee} += $dmg; $players{$player}{melee_count}++; $players{$player}{melees}{$type} += $dmg; $players{$player}{melees_count}{$type} += $dmg; $players{$player}{time_start} = $time unless $players{$player}{time_start}; $players{$player}{time_end} = $time; if ($line =~ /\((Flurry )?(Lucky )?(Critical|Crippling Blow)\)/) { $players{$player}{melee_crit} += $dmg; $players{$player}{melee_crit_count}++; } $time_start = $time unless $time_start; $time_end = $time; } sub record_spell { my ($caster, $spell) = @_; $player = check_pet($caster); $players{$player}{castcount}++ if $player eq $caster; $players{$player}{$caster}{lastspell} = $spell; $players{$player}{$caster}{nukecounted} = 0; return; } sub record_twincast { my ($player, $spell) = @_; $players{$player}{dds_count}{$spell}++ if $players{$player}{$player}{lastspell} eq $spell; return; } sub check_pet { my ($player) = @_; $player = $1 if $player =~ /^(.*)`s pet/; $player = $1 if $player =~ /^(.*)`s warder/; return $pets{$player} ? $pets{$player} : $player; } sub normalize_mob { my ($mob) = @_; return ucfirst($mob); } # sub merge_pet { # my ($pet, $master) = @_; # $pets{$pet} = $master; # foreach my $k (keys %players{$pet}) { # next if $k =~ /time.*/; # $players{$master}{$k} += $players{$pet}{$k}; # } # $players{$master}{time_start} = $players{$pet}{time_start} if $players{$pet}{time_start} < $players{$pet}{time_start}; # $players{$master}{time_end} = $players{$pet}{time_end} if $players{$pet}{time_end} > $players{$pet}{time_end}; # undef %players{$pet}; # } while (<>) { chomp; next unless /^\[([^]]*)\] (.*)$/; $time = str2time($1) unless $1 eq $rawtime; $rawtime = $1; $line = $2; if ($time_start && $time > $time_end + $interbattle_time) { print_summary(); reset_stats(); } if ($line =~ /^([\w `]+) hit ([\w `]+) for (\d+) points of non-melee damage./) { add_dd($2, $1, $3); } if ($line =~ /^([\w `]+) has taken (\d+) damage from your ([\w .-]+)\.($| \(Critical\))/) { add_dot($1, $me, $2, $3); } if ($line =~ /^([\w `]+) has taken (\d+) damage from ([\w .-]+) by ([\w `]+)./) { add_dot($1, $4, $2, $3); } if ($line =~ /^([\w `]+) (hits?|kicks?|bash(?:es)?|slash(?:es)?|bit(?:es)?|frenz(?:y|ies)|shoots?|pierces?|stabs?|sweeps?|strikes?|backstabs?|crush(?:es)?|smash(?:es)?|pieces?|claws?|punch(?:es)?) ([\w `]+) for (\d+) points of damage./) { add_melee($3, $1 eq "You" ? $me : $1, $4, $2); } elsif ($line =~ /for \d+ points of damage./) { print "missed line: $line\n" unless $line =~ /The gods have healed/; } # if ($line =~ /^([\w ]+) says, 'My leader is (\w+)./) { # merge_pet($1, $2); # } if ($line =~ /^(?:(You) begin casting |([\w ]+) begins to cast a spell. <)([\w .`-]+)(>|\.)/) { record_spell($1 eq "You" ? $me : $2, $3); } if ($line =~ /^You twincast ([\w .`-]+)\./) { record_twincast($me, $1); } } print_summary() if $time_start;