Abstract Stats Collection

Abstract the stats collection portions of DPClass so that additional
providers might easily be substituted in the future.  Initial abstract
is weak and can expand when an additional provider is actually found.
This commit is contained in:
Andrew Tomaka 2013-03-05 00:22:17 -05:00
parent 8f50d70fa5
commit 4124cd55f3
8 changed files with 285 additions and 200 deletions

View file

@ -1,6 +1,7 @@
<?php
include_once(__DIR__ . '/libs/dpclass.php');
include_once(__DIR__ . '/libs/stats.php');
include_once(__DIR__ . '/libs/diabloclass.php');
if($_POST['submit']) {
$diabloProgressUrl = trim($_POST['url']);
@ -8,15 +9,14 @@ if($_POST['submit']) {
die('Bad URL. Please enter the entire diablo progress URL.<br/><br/>Example: http://www.diabloprogress.com/hero/celanian-1548/HsuMing/21706367');
}
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $diabloProgressUrl);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$contents = curl_exec($curl);
curl_close($curl);
$character = DPClassFactory::createClassObject($contents);
$stats = StatsFactory::createStatsObject($diabloProgressUrl);
if($stats === FALSE) {
die('Bad provider. Either your provider could not be detected or we do not support your provider at this time.');
}
$character = DiabloClassFactory::createClassObject($stats->class, $stats);
if($character === FALSE) {
die('Bad class. Either your class could not be detected or we do not support your class at this time.');

View file

@ -1,2 +1,8 @@
<?php
class Barbarian extends DPClass { }
class Barbarian extends DiabloClass {
function __construct($stats) {
$this->class = $stats->class;
parent::__construct($stats);
}
}

View file

@ -1,7 +1,13 @@
<?php
class DemonHunter extends DPClass {
class DemonHunter extends DiabloClass {
function __construct($stats) {
$this->class = $stats->class;
parent::__construct($stats);
}
function EHPScore() {
$ehp = $this->getStat('EHP Unbuffed');
$ehp = $this->stats->getStat('EHP Unbuffed');
if($ehp <= 500000) {
return $ehp / 10000;
@ -15,20 +21,20 @@ class DemonHunter extends DPClass {
}
function sustainScore() {
$effectiveLs = $this->getStat('DPS Unbuffed') *
$this->getStat('Life Steal') * .2;
$mitigation = $this->getStat('EHP Unbuffed') / $this->getStat('Life');
$effectiveLs = $this->stats->getStat('DPS Unbuffed') *
$this->stats->getStat('Life Steal') * .2;
$mitigation = $this->stats->getStat('EHP Unbuffed') / $this->stats->getStat('Life');
return 1 + $mitigation * ($this->getStat('Life on Hit') *
($this->getStat('Attacks per Second') + 1) / 2 +
$this->getStat('Life per Second') + $effectiveLs) /
($this->getStat('Life') * $this->EHPScore() * 10000 /
$this->getStat('EHP Unbuffed'));
return 1 + $mitigation * ($this->stats->getStat('Life on Hit') *
($this->stats->getStat('Attacks per Second') + 1) / 2 +
$this->stats->getStat('Life per Second') + $effectiveLs) /
($this->stats->getStat('Life') * $this->EHPScore() * 10000 /
$this->stats->getStat('EHP Unbuffed'));
}
function miscScore() {
return 1 + ($this->getStat('+Maximum Discipline') / 2 +
$this->getStat('+Hatred Regenerated per Second') * 2 +
$this->getStat('+Discipline Regenerated per Second') * 15) / 100;
return 1 + ($this->stats->getStat('+Maximum Discipline') / 2 +
$this->stats->getStat('+Hatred Regenerated per Second') * 2 +
$this->stats->getStat('+Discipline Regenerated per Second') * 15) / 100;
}
}

88
libs/diabloclass.php Normal file
View file

@ -0,0 +1,88 @@
<?php
class DiabloClassFactory {
function createClassObject($class, $stats) {
if(!DiabloClassFactory::isValidClass($class)) {
return false;
}
include_once(__DIR__ . "/$class.php");
return new $class($stats);
}
private
function isValidClass($class) {
$classes = array(
'barbarian',
'demonhunter'
);
return in_array($class, $classes);
}
}
class DiabloClass {
var $class;
var $stats = array();
function __construct($stats) {
$this->stats = $stats;
}
function hallScore() {
return $this->DPSScore() * $this->EHPScore() * $this->sustainScore()
* $this->moveScore() * $this->paragonScore() * $this->miscScore();
}
function DPSScore() {
return $this->stats->getStat('DPS Unbuffed') / 1000;
}
function EHPScore() {
$ehp = $this->stats->getStat('EHP Unbuffed');
if($ehp < 1000000) {
return $ehp / 10000;
} elseif(1000000 <= $ehp && $ehp <= 2000000) {
return 100 + ($ehp - 1000000) / 20000;
} elseif(2000000 <= $ehp && $ehp <= 5000000) {
return 150 + ($ehp - 2000000) / 40000;
} elseif($ehp >= 5000000) {
return 225 + ($ehp - 5000000) / 100000;
}
}
function sustainScore() {
$effectiveLS = $this->stats->getStat('DPS Unbuffed') *
$this->stats->getStat('Life Steal') * .5;
$mitigation = $this->stats->getStat('EHP Unbuffed') / $this->stats->getStat('Life');
$rawSustainScore = 1 + $mitigation * ($this->stats->getStat('Life on Hit') *
(1 + ($this->stats->getStat('Attacks per Second') - 1) / 2) +
$effectiveLS + $this->stats->getStat('Life per Second')) /
($this->stats->getStat('Life') * $this->EHPScore() * 10000 /
$this->stats->getStat('EHP Unbuffed'));
if($rawSustainScore <= 1.5) {
return $rawSustainScore;
} elseif(1.5 < $rawSustainScore && $rawSustainScore <= 2) {
return 1.5 + ($rawSustainScore - 1.5) / 2;
} elseif(2 < $rawSustainScore && $rawSustainScore <= 3) {
return 1.75 + ($rawSustainScore - 2) / 4;
} else {
return 2 + ($rawSustainScore - 3) / 10;
}
}
function moveScore() {
return ($this->stats->getStat('Movement Speed') > .25) ? 1.25 : 1 + $this->stats->getStat('Movement Speed');
}
function paragonScore() {
return 1 + $this->stats->getStat('Paragon Level') / 2 / 100;
}
function miscScore() {
return 1;
}
}

View file

@ -0,0 +1,90 @@
<?php
class DiabloProgressStats extends Stats {
function parse() {
$this->class = $this->findClass();
$this->parseStats();
}
protected
function isParagonMaxed() {
return $this->getStat('Paragon Level') == 100;
}
private
function findClass() {
preg_match('{<span class="diablo_.*?">(.*?)</span>}', $this->html, $class);
return str_replace(' ', '', strtolower($class[1]));
}
function parseStats() {
preg_match_all('{<div class="char_attr"><nobr><span class="char_attr_name">(.*?):<\/span> <span class="char_attr_value">(.*?)<\/span><\/nobr><\/div>}', $this->html, $attributes);
for($i = 0; $i < count($attributes[0]); $i++) {
$attributes[2][$i] = str_replace(',', '', $attributes[2][$i]);
if(preg_match('/%/', $attributes[2][$i]) > 0) {
$attributes[2][$i] = str_replace('%', '', $attributes[2][$i]);
$attributes[2][$i] /= 100;
}
$this->stats[$attributes[1][$i]] = $attributes[2][$i];
}
$this->stats['Gem Life'] = $this->calculateGemLife();
$this->modifyExpBonus();
$this->stats['All Elemental Damage'] = $this->elementalDamage();
$this->modifyDPSUnbuffed();
$this->modifyEHP();
$this->modifyHP();
}
function elementalDamage() {
$totalElemental = 0;
foreach($this->stats as $stat => $value) {
if(preg_match('/\+DPS \(.*\)/', $stat) > 0) {
$totalElemental += $value;
}
}
return ($totalElemental > 0) ? $totalElemental : 0;
}
function calculateGemLife() {
if($this->isParagonMaxed()) return 0;
switch($this->getStat('Exp Bonus')) {
case .19: return .12;
case .21: return .14;
case .25: return .15;
case .27: return .16;
case .29: return .17;
case .31: return .18;
default: return 0;
}
}
function modifyExpBonus() {
if($this->getStat('Exp Bonus') >= .35) {
$this->stats['Exp Bonus'] = $this->getStat('Exp Bonus') - .35;
}
}
function modifyDPSUnbuffed() {
$this->stats['DPS Unbuffed'] = $this->getStat('DPS Unbuffed') *
max(1, 1 + ($this->getStat('+DPS Against Elites') / 2));
}
function modifyEHP() {
$this->stats['EHP Unbuffed'] = $this->getStat('EHP Unbuffed') *
(1 + $this->getStat('Life Bonus') + $this->getStat('Gem Life')) /
(1 + $this->getStat('Life Bonus'));
}
function modifyHP() {
$this->stats['Life'] = $this->getStat('Life') *
(1 + $this->getStat('Life Bonus') + $this->getStat('Gem Life')) /
(1 + $this->getStat('Life Bonus'));
}
}

View file

@ -1,177 +0,0 @@
<?php
class DPClassFactory {
function createClassObject($characterPage) {
$class = DPClassFactory::findClass($characterPage);
include_once(__DIR__ . "/$class.php");
switch($class) {
case 'barbarian':
return new Barbarian($characterPage);
case 'demonhunter':
return new DemonHunter($characterPage);
default:
return false;
}
}
private
function findClass($characterPage) {
preg_match('{<span class="diablo_.*?">(.*?)</span>}', $characterPage, $class);
return str_replace(' ', '', strtolower($class[1]));
}
}
class DPClass {
var $dpHTML;
var $class;
var $stats = array();
var $items = array();
function __construct($characterPage) {
$this->dpHTML = $characterPage;
$this->class = get_class($this);
$this->parseStats();
}
function hallScore() {
return $this->DPSScore() * $this->EHPScore() * $this->sustainScore()
* $this->moveScore() * $this->paragonScore() * $this->miscScore();
}
function DPSScore() {
return $this->getStat('DPS Unbuffed') / 1000;
}
function EHPScore() {
$ehp = $this->getStat('EHP Unbuffed');
if($ehp < 1000000) {
return $ehp / 10000;
} elseif(1000000 <= $ehp && $ehp <= 2000000) {
return 100 + ($ehp - 1000000) / 20000;
} elseif(2000000 <= $ehp && $ehp <= 5000000) {
return 150 + ($ehp - 2000000) / 40000;
} elseif($ehp >= 5000000) {
return 225 + ($ehp - 5000000) / 100000;
}
}
function sustainScore() {
$effectiveLS = $this->getStat('DPS Unbuffed') *
$this->getStat('Life Steal') * .5;
$mitigation = $this->getStat('EHP Unbuffed') / $this->getStat('Life');
$rawSustainScore = 1 + $mitigation * ($this->getStat('Life on Hit') *
(1 + ($this->getStat('Attacks per Second') - 1) / 2) +
$effectiveLS + $this->getStat('Life per Second')) /
($this->getStat('Life') * $this->EHPScore() * 10000 /
$this->getStat('EHP Unbuffed'));
if($rawSustainScore <= 1.5) {
return $rawSustainScore;
} elseif(1.5 < $rawSustainScore && $rawSustainScore <= 2) {
return 1.5 + ($rawSustainScore - 1.5) / 2;
} elseif(2 < $rawSustainScore && $rawSustainScore <= 3) {
return 1.75 + ($rawSustainScore - 2) / 4;
} else {
return 2 + ($rawSustainScore - 3) / 10;
}
}
function moveScore() {
return ($this->getStat('Movement Speed') > .25) ? 1.25 : 1 + $this->getStat('Movement Speed');
}
function paragonScore() {
return 1 + $this->stats['Paragon Level'] / 2 / 100;
}
function miscScore() {
return 1;
}
protected
function isParagonMaxed() {
return $this->getStat('Paragon Level') == 100;
}
function getStat($name) {
return (isset($this->stats[$name])) ? $this->stats[$name] : 0;
}
private
function parseStats() {
preg_match_all('{<div class="char_attr"><nobr><span class="char_attr_name">(.*?):<\/span> <span class="char_attr_value">(.*?)<\/span><\/nobr><\/div>}', $this->dpHTML, $attributes);
for($i = 0; $i < count($attributes[0]); $i++) {
$attributes[2][$i] = str_replace(',', '', $attributes[2][$i]);
if(preg_match('/%/', $attributes[2][$i]) > 0) {
$attributes[2][$i] = str_replace('%', '', $attributes[2][$i]);
$attributes[2][$i] /= 100;
}
$this->stats[$attributes[1][$i]] = $attributes[2][$i];
}
$this->stats['Gem Life'] = $this->calculateGemLife();
$this->modifyExpBonus();
$this->stats['All Elemental Damage'] = $this->elementalDamage();
$this->modifyDPSUnbuffed();
$this->modifyEHP();
$this->modifyHP();
}
function elementalDamage() {
$totalElemental = 0;
foreach($this->stats as $stat => $value) {
if(preg_match('/\+DPS \(.*\)/', $stat) > 0) {
$totalElemental += $value;
}
}
return ($totalElemental > 0) ? $totalElemental : 0;
}
function calculateGemLife() {
if($this->isParagonMaxed()) return 0;
switch($this->getStat('Exp Bonus')) {
case .19: return .12;
case .21: return .14;
case .25: return .15;
case .27: return .16;
case .29: return .17;
case .31: return .18;
default: return 0;
}
}
function modifyExpBonus() {
if($this->getStat('Exp Bonus') >= .35) {
$this->stats['Exp Bonus'] = $this->getStat('Exp Bonus') - .35;
}
}
function modifyDPSUnbuffed() {
$this->stats['DPS Unbuffed'] = $this->getStat('DPS Unbuffed') *
max(1, 1 + ($this->getStat('+DPS Against Elites') / 2));
}
function modifyEHP() {
$this->stats['EHP Unbuffed'] = $this->getStat('EHP Unbuffed') *
(1 + $this->getStat('Life Bonus') + $this->getStat('Gem Life')) /
(1 + $this->getStat('Life Bonus'));
}
function modifyHP() {
$this->stats['Life'] = $this->getStat('Life') *
(1 + $this->getStat('Life Bonus') + $this->getStat('Gem Life')) /
(1 + $this->getStat('Life Bonus'));
}
}

63
libs/stats.php Normal file
View file

@ -0,0 +1,63 @@
<?php
class StatsFactory {
function createStatsObject($characterPageUrl) {
$provider = StatsFactory::findProvider($characterPageUrl);
if(!StatsFactory::isValidProvider($provider)) {
return false;
}
include_once(__DIR__ . '/' . $provider . '_stats.php');
$class = $provider . 'Stats';
return new $class($characterPageUrl);
}
private
function findProvider($characterPageUrl) {
preg_match('{.*\.(.*)\..*/.*}', $characterPageUrl, $provider);
return strtolower($provider[1]);
}
function isValidProvider($provider) {
$providers = array(
'diabloprogress'
);
return in_array($provider, $providers);
}
}
class Stats {
private $url;
protected $html;
public $stats = array();
public $class;
function __construct($characterPageUrl) {
$this->url = $characterPageUrl;
$this->html = $this->getPageContents();
$this->parse();
}
function getStat($name) {
return (isset($this->stats[$name])) ? $this->stats[$name] : 0;
}
private
function getPageContents() {
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $this->url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$contents = curl_exec($curl);
curl_close($curl);
//handle httpCodes and other failures
return $contents;
}
}

9
stats_test.php Normal file
View file

@ -0,0 +1,9 @@
<?php
include_once(__DIR__ . '/libs/stats.php');
$stats = StatsFactory::createStatsObject('http://www.diabloprogress.com/hero/celanian-1548/HsuMing/21706367');
print_r($stats->class);
echo "\n\n";
print_r($stats->stats);
echo "\n\n";