[[], CLISetup::ARGV_PARAM, 'Compiles player stats into file for the character profiler tool.'] ); protected $worldDependency = ['player_levelstats', 'player_classlevelstats']; protected $dbcSourceFiles = ['gtchancetomeleecrit', 'gtchancetomeleecritbase', 'gtchancetospellcrit', 'gtchancetospellcritbase', 'gtoctregenhp', 'gtregenmpperspt', 'gtregenhpperspt']; protected $requiredDirs = ['datasets/']; public function generate() : bool { $sub = ['classs', 'race', 'combo', 'level', 'skills']; $out = []; foreach ($sub as $s) { if ($out[$s] = $this->$s()) continue; CLI::write('[statistics] generator '.$s.'() returned empty', CLI::LOG_WARN); $this->success = false; } $toFile = 'g_statistics = '.preg_replace('/"\$([^$"]+)"/', '\1', Util::toJSON($out)).';'; if (!CLISetup::writeFile('datasets/statistics', $toFile)) $this->success = false; return $this->success; } // constants and mods taken from TrinityCore (Player.cpp, StatSystem.cpp) private function classs() : array { /* content per Index mleatkpwr[base, strMultiplier, agiMultiplier, levelMultiplier] rngatkpwr[base, strMultiplier, agiMultiplier, levelMultiplier] baseCritPct[phys, spell] diminishingConstant baseDodgePct DodgeCap baseParryPct ParryCap baseBlockPct classMod1 applies mod directly only one class having something worth mentioning: DK classMod2 applies mod directly so what were they originally used for..? */ $dataz = array( 1 => [[-20, 2, 0, 3], [-10, 0, 1, 1], null, 0.9560, 3.6640, 88.129021, 5, 47.003525, 5, [], []], 2 => [[-20, 2, 0, 3], [-10, 0, 1, 0], null, 0.9560, 3.4943, 88.129021, 5, 47.003525, 5, [], []], 3 => [[-20, 1, 1, 2], [-10, 0, 1, 2], null, 0.9880, -4.0873, 145.560408, 5, 145.560408, 0, [], []], 4 => [[-20, 1, 1, 2], [-10, 0, 1, 1], null, 0.9880, 2.0957, 145.560408, 5, 145.560408, 0, [], []], 5 => [[-10, 1, 0, 0], [-10, 0, 1, 0], null, 0.9830, 3.4178, 150.375940, 0, 0.0, 0, [], []], 6 => [[-20, 2, 0, 3], [-10, 0, 1, 0], null, 0.9560, 3.6640, 88.129021, 5, 47.003525, 0, [], ['parryrtng' => [0.25, 'percentOf', 'str']]], // Forcefull Deflection (49410) 7 => [[-20, 1, 1, 2], [-10, 0, 1, 0], null, 0.9880, 2.1080, 145.560408, 0, 145.560408, 5, [], []], 8 => [[-10, 1, 0, 0], [-10, 0, 1, 0], null, 0.9830, 3.6587, 150.375940, 0, 0.0, 0, [], []], 9 => [[-10, 1, 0, 0], [-10, 0, 1, 0], null, 0.9830, 2.4211, 150.375940, 0, 0.0, 0, [], []], 11 => [[-20, 2, 0, 0], [-10, 0, 1, 0], null, 0.9720, 5.6097, 116.890707, 0, 0.0, 0, [], []] ); foreach ($dataz as $class => &$data) $data[2] = array_values(DB::Aowow()->selectRow('SELECT mle.chance*100 cMle, spl.chance*100 cSpl FROM dbc_gtchancetomeleecritbase mle, dbc_gtchancetospellcritbase spl WHERE mle.idx = spl.idx AND mle.idx = ?d', $class - 1)); return $dataz; } // { str, agi, sta, int, spi, raceMod1, raceMod2 } private function race() : array { $raceData = DB::World()->select('SELECT `race` AS ARRAY_KEY, MIN(`str`), MIN(`agi`), MIN(`sta`), MIN(`inte`), MIN(`spi`) FROM player_levelstats WHERE `level` = 1 GROUP BY `race` ORDER BY `race` ASC'); foreach ($raceData as &$rd) $rd = array_values($rd + [[], []]); $racials = new SpellList(array(['typeCat', -4], ['reqClassMask', 0])); $allMods = $racials->getProfilerMods(); foreach ($allMods as $spellId => $mods) { if (!$mods) continue; // if there is ever a case where a racial is shared between races i don't want to know about it! $raceId = log($racials->getEntry($spellId)['reqRaceMask'], 2) + 1; if (!isset($raceData[$raceId])) continue; foreach ($mods as $jsonStat => $mod) { if (empty($raceData[$raceId][5][$jsonStat])) $raceData[$raceId][5][$jsonStat] = $mod; else $raceData[$raceId][6][$jsonStat] = $mod; } } return $raceData; } // TrinityCore claims, DodgePerAgi per level and class can be constituted from critPerAgi (and level (and class)) // who am i to argue // rebase stats to a specific race. chosen human as all stats are 20 and tauren for hunter, shaman and druid // level:{ str, agi, sta, int, spi, hp, mana, mleCrt%Agi, splCrt%Int, dodge%Agi, HealthRegenModToBaseStat, HealthRegenModToBonusStat } private function combo() : array { $result = []; $critToDodge = array( 1 => 0.85/1.15, 2 => 1.00/1.15, 3 => 1.11/1.15, 4 => 2.00/1.15, 5 => 1.00/1.15, 6 => 0.85/1.15, 7 => 1.60/1.15, 8 => 1.00/1.15, 9 => 0.97/1.15, 11 => 2.00/1.15 ); foreach ($critToDodge as $class => $mod) { // humans can't be hunter, shaman, druids (use tauren here) if (in_array($class, [3, 7, 11])) $offset = array_values(DB::World()->selectRow('SELECT MIN(`str`), MIN(`agi`), MIN(`sta`), MIN(`inte`), MIN(`spi`) FROM player_levelstats WHERE `level` = 1 AND `race` = 6')); else $offset = array_values(DB::World()->selectRow('SELECT MIN(`str`), MIN(`agi`), MIN(`sta`), MIN(`inte`), MIN(`spi`) FROM player_levelstats WHERE `level` = 1 AND `race` = 1')); $gtData = DB::Aowow()->select( 'SELECT mlecrt.idx - ?d AS ARRAY_KEY, mlecrt.chance * 100, splcrt.chance * 100, mlecrt.chance * 100 * ?f, baseHP5.ratio * 1, extraHP5.ratio * 1 FROM dbc_gtchancetomeleecrit mlecrt JOIN dbc_gtchancetospellcrit splcrt ON splcrt.idx = mlecrt.idx JOIN dbc_gtoctregenhp baseHP5 ON baseHP5.idx = mlecrt.idx JOIN dbc_gtregenhpperspt extraHP5 ON extraHP5.idx = mlecrt.idx WHERE mlecrt.idx BETWEEN ?d AND ?d', (($class - 1) * 100) - 1, // class-offset $mod, (($class - 1) * 100) + 0, // lvl 1 (($class - 1) * 100) + 79 // lvl 80 ); $rows = DB::World()->select( 'SELECT pls.level AS ARRAY_KEY, pls.str - ?d, pls.agi - ?d, pls.sta - ?d, pls.inte - ?d, pls.spi - ?d, pcls.basehp, IF(pcls.basemana <> 0, pcls.basemana, 100) FROM player_levelstats pls JOIN player_classlevelstats pcls ON pls.level = pcls.level AND pls.class = pcls.class WHERE pls.race = ?d AND pls.class = ?d ORDER BY pls.level ASC', $offset[0], $offset[1], $offset[2], $offset[3], $offset[4], in_array($class, [3, 7, 11]) ? 6 : 1, $class ); $result[$class] = []; foreach ($rows as $lvl => $row) $result[$class][$lvl] = array_values(array_merge($row, $gtData[$lvl])); } return $result; } // base mana regeneration per level // identical across classes (just use one, that acutally has mana (offset: 100)) // content of gtRegenMPPerSpt.dbc private function level() : array { return DB::Aowow()->selectCol('SELECT idx-99 AS ARRAY_KEY, ratio FROM dbc_gtregenmpperspt WHERE idx >= 100 AND idx < 100 + ?d', MAX_LEVEL); } // profession perks ... too lazy to formulate a search algorithm for two occurences private function skills() : array { // DB::Aowow()->select( // 'SELECT sk.id AS "skillId", sla.reqSkillLevel, s.effect1AuraId AS "auraId", s.effect1MiscValue, s.effect1BasePoints + s.effect1DieSides AS "qty" // FROM dbc_skilllineability sla // JOIN dbc_skillline sk ON sk.id = sla.skilllineid // JOIN dbc_spell s ON s.id = sla.spellId // WHERE sla.acquiremethod = 1 AND // learn on skillup // sk.categoryId = 11 AND // primary profession // s.effect1Id = 6 AND // ApplyAura // s.effect1AuraId IN (29, 189) // ModStatFlat, ModRating (need more?) // '); return array( SKILL_MINING => array( // mining / toughness 75 => ['sta' => 3], 150 => ['sta' => 5], 225 => ['sta' => 7], 300 => ['sta' => 10], 375 => ['sta' => 30], 450 => ['sta' => 60], ), SKILL_SKINNING => array( // skinning / master of anatomy 75 => ['critstrkrtng' => 3], 150 => ['critstrkrtng' => 6], 225 => ['critstrkrtng' => 9], 300 => ['critstrkrtng' => 12], 375 => ['critstrkrtng' => 20], 450 => ['critstrkrtng' => 40], ) ); } }); ?>