From 41c6933c66b509809299c9b7e6a4d0172432a78b Mon Sep 17 00:00:00 2001 From: PViddy72 <158861702+PViddy72@users.noreply.github.com> Date: Sun, 7 Apr 2024 12:54:55 -0500 Subject: [PATCH] Restore UnitDB function - Update and improve displayed data (#18) * Improved Error handling Improved error handling. Detect when commands fail. Exit script on failure. Removed use of shell_exec (it does not detect command failures). * Restore UnitDB function + Update/Improve Unit data Correct script errors related to differences between PHP 8.x and earlier versions. Example: count() generates an error if the argument is not an array object (string being passed). Previously, a string argument would return a 1. Correct update.php to read in blueprint data in the correct order (3599 files serve as a baseline, and must be read first). Improved error handling. Add code to properly handle reformatted blueprints. Overhaul of unit DPS and fire cycle calculations to improve accuracy. General improvements in accuracy and completeness of unit data. * Code Cleanup Requested changes of 2024-04-05 (code cleanup). Corrected DPS code for Salvation. * Requested change Updated comment per request. --- www/include/Git.php | 20 ++- www/index.php | 5 +- www/res/scripts/calculations.php | 80 +++++++---- www/res/scripts/functions.php | 222 ++++++++++++++++++------------- www/update.php | 75 +++++++---- 5 files changed, 251 insertions(+), 151 deletions(-) diff --git a/www/include/Git.php b/www/include/Git.php index 3906505..8431641 100644 --- a/www/include/Git.php +++ b/www/include/Git.php @@ -1,5 +1,6 @@ execute("git clone --filter=blob:none --depth 1 --sparse {$escRepo} --branch {$escBranch} {$escDestDir}"); + $out = $this->execute("git clone --filter=blob:none --depth 1 --sparse {$escRepo} --branch {$escBranch} {$escDestDir}"); foreach ($sparseFolders as $folder) { $escFolder = escapeshellarg($folder); - $this->execute("cd {$escDestDir} && git sparse-checkout add {$escFolder}"); + $out = $this->execute("cd {$escDestDir} && git sparse-checkout add {$escFolder}"); } } else { - $this->execute("git clone {$escRepo} {$escDestDir}"); + $out = $this->execute("git clone {$escRepo} {$escDestDir}"); + print_r($out); } } private function execute($command) { - $output = shell_exec($command); + $output = null; + $retval = null; + logDebug($command); + exec($command, $output, $retval); + + if ($retval != 0) { + logDebug("Returned with status $retval and output: \n"); + print_r($output); + exit ($retval); + } + return $output; } } diff --git a/www/index.php b/www/index.php index a615f07..42bc095 100755 --- a/www/index.php +++ b/www/index.php @@ -1,4 +1,7 @@ - \ No newline at end of file + diff --git a/www/res/scripts/calculations.php b/www/res/scripts/calculations.php index a018ef9..73f3a44 100644 --- a/www/res/scripts/calculations.php +++ b/www/res/scripts/calculations.php @@ -1,28 +1,50 @@ MuzzleSalvoSize; - if ($mss == 1){ - $bones = 0; - foreach($weapon->RackBones as $rack){ - $bones += count((array)$rack->MuzzleBones); + + /// Now we need determine if the weapon fires from multiple muzzles simultaneously (or close to it). + if (property_exists($weapon, 'RackFireTogether') && ($weapon->RackFireTogether == 1)) { + /// Count up all the muzzles in all the racks + foreach ($weapon->RackBones as $rack) { + $MuzzleCount += count((array)$rack->MuzzleBones); } - return $bones; + $firecycle = $mss * $MuzzleCount; } - else{ - return $mss * count((array)$weapon->RackBones); + // Do all real weapons have MuzzleSalvoDelay? + // else if (property_exists($weapon, 'MuzzleSalvoDelay') && $weapon->MuzzleSalvoDelay == 0) { + else if ($weapon->MuzzleSalvoDelay == 0) { + /// Count only the muzzles in the first rack, since RackFireTogether is false + /// We do not use MuzzleSalvoSize (mss) when MuzzleSalvoDelay is 0. + /// Cast to an array, in case it is only a string instead of an array, (PHP 8) + $MuzzleCount = count((array)$weapon->RackBones[0]->MuzzleBones); + $firecycle = $MuzzleCount; + } else { + $firecycle = $mss; } + } + + return $firecycle; + } // Source : https://github.com/spooky/unitdb/blob/master/app/js/dps.js // (calculations provided by Exotic_retard) - function calculateDps($stdClassWeapon, $unitID){ + function calculateDps($stdClassWeapon, $unitID, $Projectile){ $weapon = arrayCastRecursive($stdClassWeapon); // StdClass are a PAIN to use in PHP // Hardcoded exceptions $specials = [ - 'UEL0103', // lobo - 'XSL0103', // zthuee 'DAA0206', // mercy 'XAA0306' // solace ]; @@ -31,7 +53,7 @@ function calculateDps($stdClassWeapon, $unitID){ $shots = 1; - if (isset($weapon["MuzzleSalvoSize"])) $shots = calculateFireCycle($stdClassWeapon); + if (isset($weapon["MuzzleSalvoSize"])) $shots = calculateFireCycle($stdClassWeapon, $unitID); // fall back to the old calculation formula for the special snowflakes @@ -46,7 +68,7 @@ function calculateDps($stdClassWeapon, $unitID){ // in theory if your total MuzzleSalvoDelay is longer than the reload time your weapon waits for the reload time twice, // but thats pretty much a bug so not taken into account here - + /// BTW - SpookyDB uses round(), not floor(), based on the values seen there. Values will not match between DBs. floor() is the correct method. $trueReload = max(0.1*floor(10 / $weapon["RateOfFire"]), 0.1); $trueReload = max( ($weapon["RackSalvoChargeTime"] ?? 0) + ($weapon["RackSalvoReloadTime"] ?? 0) + @@ -54,22 +76,32 @@ function calculateDps($stdClassWeapon, $unitID){ $trueReload ); - $trueSalvoSize = 1; - if (($weapon["MuzzleSalvoDelay"] ?? 0) > 0) { // if theres no muzzle delay, all muzzles fire at the same time - $trueSalvoSize = ($weapon["MuzzleSalvoSize"] ?? 1); - } else if ($weapon["RackBones"] && count($weapon["RackBones"]) > 0) { // dummy weapons dont have racks - if ($weapon["RackFireTogether"]) { - $trueSalvoSize = count($weapon["RackBones"]) * count($weapon["RackBones"][0]["MuzzleBones"]); - } else if (count($weapon["RackBones"]) > 0) { - $trueSalvoSize = count($weapon["RackBones"][0]["MuzzleBones"]); +/* + Code for calculating missile/projectile count should only be done in calculateFireCycle. + We don't want to calculate fire cycles in two places. Just use the value returned from that function. +*/ + $trueSalvoSize = $shots; + + $trueDamage = $weapon["Damage"] * ($weapon["DoTPulses"] ?? 1) + ($weapon["InitialDamage"] ?? 0); + + /// For weapons with fragmentation shells (Lobo, Zthuee, Salvation). + if (isset($Projectile) && property_exists($Projectile->Physics, 'Fragments')) { + $trueSalvoSize = $trueSalvoSize * $Projectile->Physics->Fragments; + /// Exception for Salvation + if ($unitID == "XAB2307") { + /// Salvation uses a shell that fragments into 6 shells, which then fragments into 6 more. + /// Only first fragmentation is accounted for above. Hard code the 2nd one by multiplying by 6. + $trueSalvoSize = $trueSalvoSize * 6; } + } - $trueDamage = $weapon["Damage"]*($weapon["DoTPulses"] ?? 1) + ($weapon["InitialDamage"] ?? 0); - // beam weapons are a thing and do their own thing. yeah good luck working out that. - $trueDamage = max((floor(($weapon["BeamLifetime"] ?? 0) / (($weapon["BeamCollisionDelay"] ?? 0)+0.1))+1)*$weapon["Damage"], $trueDamage); + $trueDamage = max((floor(($weapon["BeamLifetime"] ?? 0) / (($weapon["BeamCollisionDelay"] ?? 0) + 0.1)) + 1) * $weapon["Damage"], $trueDamage); + if ($trueSalvoSize == 0) $trueSalvoSize = 1; // Adjustment needed for beam weapons + $salvoDamage = $trueSalvoSize * $trueDamage * ($isSpecial ? $shots : 1); + $trueDPS = ($salvoDamage / $trueReload); return $trueDPS; diff --git a/www/res/scripts/functions.php b/www/res/scripts/functions.php index 3451ad6..e403900 100755 --- a/www/res/scripts/functions.php +++ b/www/res/scripts/functions.php @@ -1,19 +1,38 @@ '; + print_r($val); + echo ''; +} + +// For some values, I want to see the decimals, unless they don't exist. +function number_format_unlimited_precision($number, $decimal = '.') +{ + $broken_number = explode($decimal, $number); + if (count($broken_number) == 2) { + return number_format($broken_number[0]) . $decimal . $broken_number[1]; + } else { + return number_format($broken_number[0]); + } +} + function categorizeUnitData($categoriesOrder, $userSettings, $dataUnits){ $finalData = array(); @@ -26,16 +45,17 @@ function categorizeUnitData($categoriesOrder, $userSettings, $dataUnits){ /// This chunk of code can be used to skip units lacking an icon, a preview, or basic general information. /// Useful to avoid displaying debug units. - /* + /// Filter is >necessary<, in fact, because some of these units can cause the script to fail. + /// Added filter to prevent units that don't have a 'faction'. if (!property_exists($item, 'StrategicIconName') || !property_exists($item, 'General') || - !property_exists($item->General, 'Icon')){ + !property_exists($item->General, 'Icon') || + !property_exists($item->General, 'FactionName')) { continue; } - */ /// Formatting the Faction name to ensure it has good casing : Aeon, Cybran, Seraphim, UEF, Nomads - $faction = formatFaction($item->General->FactionName); + $faction = formatFaction($item->General->FactionName); /// Adding the army to the list, given the user said he wanted to see it. if (!in_array($faction, $armies) && in_array($faction, $userSettings['showArmies'])){ @@ -501,10 +521,11 @@ function displayBuildlist($info, $thisUnit, $userSettings, $dataUnits, $dataLoc) /// Checks every unit in the $data to see which ones correspond to the right buildable category /// and populates the $buildable with them foreach($dataUnits as $unit){ - foreach($thisUnit->Economy->BuildableCategory as $buildableCategory){ + /// (array) Type cast needed in case BuildableCategory is only a string (for PHP 8) + foreach((array)$thisUnit->Economy->BuildableCategory as $buildableCategory){ $buildableRequirements = explode(' ', $buildableCategory); $canBuild = true; - foreach($buildableRequirements as $requirement){ + foreach((array)$buildableRequirements as $requirement){ if (!in_array($requirement, $unit->Categories) && strtoupper($requirement) != strtoupper($unit->Id)){ $canBuild = false; } @@ -874,21 +895,48 @@ function displayWeapon($thisWeapon, $info, $thisUnit, $dataLoc, $userSettings, $ '.($thisWeapon->DamageType).' '; - } + } + + + /// If the weapon uses a missile, and we have this missile in the $data, lets display more information about the missile + /// We need to find the missile/projectile before the DPS calculation, in case it uses a fragmentation weapon. + $WeaponProjectile = NULL; + + if (property_exists($thisWeapon, 'ProjectileId')) { + + $foundArr = []; + $found = preg_match('~(?<=projectiles\/).*(?=\/)~', $thisWeapon->ProjectileId, $foundArr); + + if ($found) { + + $projectileId = $foundArr[0]; + /// There has got to be a better way to do this, right? Should make an array for projectiles at some future point. + if (strlen($projectileId) > 0) { + /// searching the needle in the haystack... + foreach ($dataMissiles as $thisMissile) { + if (strtoupper($thisMissile->Id) == strtoupper($projectileId)) { + // found it ! + $WeaponProjectile = $thisMissile; + break; + } + } + } + } + } - /// Approximate DPS + /// Approximate DPS if (property_exists($thisWeapon, 'Damage') && $thisWeapon->Damage > 0 && - $thisWeapon->WeaponCategory != "Death"){ + $thisWeapon->WeaponCategory != "Death"){ echo '
Approximate DPS
- '.format(calculateDps($thisWeapon, $info['Id'])).' + '.format(calculateDps($thisWeapon, $info['Id'], $WeaponProjectile)).'
'; - } + } /// Specific Damage styled display if the weapon has damage if (property_exists($thisWeapon, 'Damage') && @@ -931,7 +979,7 @@ function displayWeapon($thisWeapon, $info, $thisUnit, $dataLoc, $userSettings, $ Fire cycle
- '.calculateFireCycle($thisWeapon).' projectiles / shot + '.calculateFireCycle($thisWeapon, $info['Id']).' projectiles / shot
'; } @@ -976,75 +1024,55 @@ function displayWeapon($thisWeapon, $info, $thisUnit, $dataLoc, $userSettings, $ Nuke damage
- '.format($thisWeapon->NukeOuterRingDamage).'-'.format($thisWeapon->NukeInnerRingDamage).' + '.format($thisWeapon->NukeInnerRingDamage).'-'.format($thisWeapon->NukeOuterRingDamage).'
'; } - - /// If the weapon uses a missile, and we have this missile in the $data, lets display more information about the missile - if (property_exists($thisWeapon, 'ProjectileId')){ - - $foundArr = []; - $found = preg_match('~(?<=projectiles\/).*(?=\/)~', $thisWeapon->ProjectileId, $foundArr); - - if ($found){ - - $projectileId = $foundArr[0]; - - if (strlen($projectileId) > 0){ - /// searching the needle in the haystack... - foreach($dataMissiles as $thisMissile){ - if ($thisMissile->Id == strtoupper($projectileId)){ - // found it ! - - /// Display the blueprint + github link - echo ' -
-
- Missile ID/BP -
-
- - '.($projectileId).' - -
-
'; - - /// Display cost if any - if (property_exists($thisMissile, 'Economy')){ - $eco = $thisMissile->Economy; - echo ' -
-
- Missile Cost -
-
-
-
- nrg '.format($eco->BuildCostEnergy).' - Energy cost -
-
- mss '.format($eco->BuildCostMass).' - Mass cost -
-
- tim '.format($eco->BuildTime).' - Build time -
-
-
-
'; - } - break; - } - } - - } - } + + + /// Display the blueprint + github link + if (isset($projectileId)) { + echo ' +
+
+ Missile ID/BP +
+
+ + ' . ($projectileId) . ' + +
+
'; } + /// Display cost if any + if (isset($WeaponProjectile) && property_exists($WeaponProjectile, 'Economy')){ + $eco = $WeaponProjectile->Economy; + echo ' +
+
+ Missile Cost +
+
+
+
+ nrg '.format($eco->BuildCostEnergy).' + Energy cost +
+
+ mss '.format($eco->BuildCostMass).' + Mass cost +
+
+ tim '.format($eco->BuildTime).' + Build time +
+
+
+
'; + } + /// Displaying every generic property now foreach($propertiesToDisplayGreaterThanZero as $thisProp){ if (property_exists($thisWeapon, $thisProp) && @@ -1054,7 +1082,7 @@ function displayWeapon($thisWeapon, $info, $thisUnit, $dataLoc, $userSettings, $ '.caseFormat($thisProp).'
- '.format($thisWeapon->$thisProp).' + '.number_format_unlimited_precision($thisWeapon->$thisProp).'
'; } @@ -1067,7 +1095,7 @@ function displayWeapon($thisWeapon, $info, $thisUnit, $dataLoc, $userSettings, $ '.caseFormat($thisProp).'
- '.format($thisWeapon->$thisProp).' + '.number_format_unlimited_precision($thisWeapon->$thisProp).'
'; } @@ -1079,7 +1107,7 @@ function displayWeapon($thisWeapon, $info, $thisUnit, $dataLoc, $userSettings, $ '.caseFormat($thisProp).'
- '.format($thisWeapon->$thisProp).' + '.number_format_unlimited_precision($thisWeapon->$thisProp).'
'; } @@ -1393,6 +1421,7 @@ function displayUnitSupport($info, $thisUnit){ color:'.getFactionColor($info['Faction'], 'bright').';"> Vision : '.($intel->VisionRadius).' '; + if (property_exists($intel, 'RadarRadius')) echo '
Intel, "WaterVisionRadius") || property_exists($thisUnit->Intel, "OmniRadius"))) - ){ + ) + { echo '
'; @@ -1462,7 +1492,7 @@ function displayUnitSupport($info, $thisUnit){ "OmniRadius" ); - foreach($thisUnit->Intel as $key=>$value){ + foreach((array)$thisUnit->Intel as $key=>$value){ if (!in_array($key, $whitelist)){ continue; } @@ -1503,7 +1533,7 @@ function displayUnitAbilities($info, $dataLoc, $lang){
'; - foreach($abilities as $thisAb){ + foreach((array)$abilities as $thisAb){ echo '
clone($repoDir, $version); +/* End of github dependent section */ copyFolder($repoDir . "/projectiles", path($dataFolder, 'projectiles')); copyFolder($repoDir . "/units", path($dataFolder, 'units')); @@ -193,33 +209,40 @@ function locfileToPhp($locContent) unzipFiles("data/loc/loc_US.scd.3599", path($locFolder, 'loc_US.3599')); unzipFiles("data/loc/loc.nx2", path($locFolder, 'loc.nx2')); -$folders = [ 'projectiles.3599', 'units.3599', 'projectiles', 'units', 'loc_US.3599', 'loc.nx2', 'loc' ]; +$folders = [ 'projectiles.3599', 'units.3599', 'projectiles', 'units', 'loc\\loc_US.3599', 'loc\\loc.nx2']; $valid_types = [ 'unit.bp', 'proj.bp', 'db.lua' ]; $blueprints = array(); $bpFiles = array(); $locFiles = array(); -$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dataFolder)); -foreach ($iterator as $file) { - if ($file->isDir()) continue; - $filename = $file->getFilename(); - if ($filename == '.' || $filename == '..') continue; - if (strpos($filename, '_') == false) continue; - list($blueprintId, $type) = explode('_', $filename); - if (!in_array($type, $valid_types)) continue; - - $path = $file->getPathname(); - - if ($type == 'db.lua') { - // get parent folder of file - $parentFolder = basename(dirname($path)); - $lang = strtoupper($parentFolder); - $locFiles[$lang] = $path; - } - else { - $bpFiles[$blueprintId] = $path; - } +// It is >critical< that the folders are read in the order specified by the $folders variable. +// The .3599 folders must be read first, otherwise old values will appear in the results. + +foreach ($folders as $thisFolder) { + $thisPath = $dataFolder."\\".$thisFolder; + + $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($thisPath)); + foreach ($iterator as $file) { + if ($file->isDir()) continue; + $filename = $file->getFilename(); + if ($filename == '.' || $filename == '..') continue; + if (strpos($filename, '_') == false) continue; + list($blueprintId, $type) = explode('_', $filename); + if (!in_array($type, $valid_types)) continue; + + $path = $file->getPathname(); + + if ($type == 'db.lua') { + // get parent folder of file + $parentFolder = basename(dirname($path)); + $lang = strtoupper($parentFolder); + $locFiles[$lang] = $path; + } + else { + $bpFiles[$blueprintId] = $path; + } + } } $blueprint = array(); @@ -280,4 +303,4 @@ function hideUpdateMenu() {
-
\ No newline at end of file +