better item parsing, displaying in char page, needs more work

This commit is contained in:
Hash Borgir 2023-06-04 23:02:01 -06:00
parent a1f3ca5320
commit beaeb2fd49
4 changed files with 157 additions and 36 deletions

View File

@ -133,17 +133,26 @@ WHERE sk.charclass = '$class'";
public function parseItems() { public function parseItems() {
$i_TotalOffset = strpos($this->data, "JM"); // Find the offset of the "JM" marker in the data $i_TotalOffset = strpos($this->data, "JM"); // Find the offset of the "JM" marker in the data
fseek($this->fp, $i_TotalOffset + 2); // Move the file pointer to the next position after the "JM" marker fseek($this->fp, $i_TotalOffset + 2); // Move the file pointer to the next position after the "JM" marker
// get total # of items
$i_Total = unpack('S*', (fread($this->fp, 2)))[1]; // Read 2 bytes from the file and unpack them as an unsigned short (16-bit) value $i_Total = unpack('S*', (fread($this->fp, 2)))[1]; // Read 2 bytes from the file and unpack them as an unsigned short (16-bit) value
// get offsets for each item from total, so if 50, run loop 50 times
// grab the offset of each item, each item starts with JM, skim JM (+2)
$i_Offsets = []; $i_Offsets = [];
for ($i = 0; $i <= $i_Total; $i++) { for ($i = 0; $i <= $i_Total; $i++) {
$i_Offsets[] = strposX($this->data, "JM", $i + 2); // Find the offsets of the "JM" markers for each item $i_Offsets[] = strposX($this->data, "JM", $i + 2); // Find the offsets of the "JM" markers for each item
} }
// foreach item offset, get item data byte length between the JMs
// k = item #, like 0,1,2,3, etc.
// v = actual offset of item
foreach ($i_Offsets as $k => $v) { foreach ($i_Offsets as $k => $v) {
$itemOffsets[$v] = $i_Offsets[$k + 1] - $i_Offsets[$k]; // Calculate the length of each item's data by subtracting consecutive offsets $itemOffsets[$v] = $i_Offsets[$k + 1] - $i_Offsets[$k]; // Calculate the length of each item's data by subtracting consecutive offsets in bytes
} }
array_pop($itemOffsets); // Remove the last element from the itemOffsets array array_pop($itemOffsets); // Remove the last element from the itemOffsets array, not sure why
//now get items. For each itemOffsets, create new D2Item object that parses the bitstream
// readh(offset, numbytes), convert toBits, feed into D2Item
$_items = []; $_items = [];
foreach ($itemOffsets as $offset => $bytes) { foreach ($itemOffsets as $offset => $bytes) {
$this->items[] = new D2Item($this->ByteReader->toBits($this->ByteReader->readh($offset, $bytes))); // Create a new D2Item object and add it to the items array $this->items[] = new D2Item($this->ByteReader->toBits($this->ByteReader->readh($offset, $bytes))); // Create a new D2Item object and add it to the items array

View File

@ -2,6 +2,7 @@
require_once 'D2BitReader.php'; require_once 'D2BitReader.php';
require_once 'D2ItemStructureData.php'; require_once 'D2ItemStructureData.php';
require_once 'D2Strings.php';
/** /**
* *
@ -12,6 +13,7 @@ class D2Item {
* @var null * @var null
*/ */
private $bits = null; private $bits = null;
/** /**
* @var null * @var null
*/ */
@ -37,6 +39,8 @@ class D2Item {
*/ */
private function parseItem() { private function parseItem() {
$b = new D2BitReader($this->bits); $b = new D2BitReader($this->bits);
// if the item is extended or not, based on length of bits
$this->iData['extended'] = (strlen($this->bits) > 112) ? 1 : 0;
$b->skip(16); // Skip JM $b->skip(16); // Skip JM
$b->skip(4); // skip unknown 4 bytes $b->skip(4); // skip unknown 4 bytes
@ -57,16 +61,80 @@ class D2Item {
$b->skip(1); $b->skip(1);
$this->iData['runeword'] = $b->read(1); // bit 42 the item has been given a Rune Word. $this->iData['runeword'] = $b->read(1); // bit 42 the item has been given a Rune Word.
$b->skip(15); // unknown; some of these bits may be set $b->skip(15); // unknown; some of these bits may be set
// item location // item location
$location = bindec($b->readr(3)); // bit 58 parent Item location. $location = bindec($b->readr(3)); // bit 58 parent Item location.
$body = bindec($b->readr(4)); // bit 61 If the item is equipped $body = bindec($b->readr(4)); // bit 61 If the item is equipped
$col = bindec($b->readr(4)); // bit 65 Column number of the left corner of the item $col = bindec($b->readr(4)); // bit 65 Column number of the left corner of the item
$row = bindec($b->readr(4)); // bit 69 Row number of the top of the item, counting from 0. $row = bindec($b->readr(4)); // bit 69 Row number of the top of the item, counting from 0.
$_stored = bindec($b->readr(3)); // bit 73 $_stored = bindec($b->readr(3)); // bit 73
// The number of gems (or skulls or jewels) which have been glued to this item (if socketed). There will be this many additional item structures for the gems immediately following this item, in the order that the gems were inserted.
$b->seek(108);
$this->iData['gems_in'] = bindec($b->readr(3));
//This appears to be the item's level; i.e., the level with which the item was created (or 'dropped'). The item level is based on the level of the monster who dropped it, the level of the area you're in if found in a chest, or, in rare cases, your characters level. The item level determines what modifiers are allowed on the item.
$b->seek(143);
$this->iData['ilvl'] = bindec($b->readr(7));
$b->seek(150);
$quality = bindec($b->readr(4));
switch ($quality) {
case D2ItemQuality::LOW_QUALITY:
$this->iData['iquality'] = "Low Quality";
break;
case D2ItemQuality::NORMAL:
$this->iData['iquality'] = "Normal";
break;
case D2ItemQuality::HIGH_QUALITY:
$this->iData['iquality'] = "High Quality";
break;
case D2ItemQuality::MAGIC:
$this->iData['iquality'] = "Magic";
break;
case D2ItemQuality::SET:
$this->iData['iquality'] = "Set";
break;
case D2ItemQuality::RARE:
$this->iData['iquality'] = "Rare";
break;
case D2ItemQuality::UNIQUE:
$this->iData['iquality'] = "Unique";
break;
case D2ItemQuality::CRAFTED:
$this->iData['iquality'] = "Crafted";
break;
default:
$this->iData['iquality'] = "Unknown";
break;
}
// ring/amu/jew/char or not. 1 or 0. if 1, next 3 are set
// If this bit is set, the item has one of multiple pictures associated with it; the next field determines which picture a particular item uses. If this bit is 0, the next field is absent. The picture field is used for rings, amulets, jewels, and charms.
$b->seek(151);
if(bindec($b->read(1))) {
$b->seek(152);
$jarc_picture = bindec($b->readr(3));
}
// This bit apparently is set for certain class-specific Expansion Set items. It indicates the presence of the next 11-bit field. If this bit is 0, the next field is absent.
$b->seek(152);
/*Credit for the discovery of this field's meaning goes entirely to Guillaume Courtin of France. Thanks! :->
This field indicates magic properties which are inherent in certain class-specific items. A given class-specific item will (almost) always start with the same set of properties, even if its quality is "normal". Other quality ratings may add more properties to the standard set. It appears that items which will have this field are:
Amazon-only bows, spears, and javelins
Voodoo heads (Necromancer-only shields)
Paladin-only shields
Orbs (Sorceress-Only wands)
*/
if(bindec($b->read(1))) {
$b->seek(153);
$class_specific = bindec($b->readr(11));
}
// weird behavior/bug // weird behavior/bug
// if item is in a container, bodypart will be NULL // if item is in a container, bodypart will be NULL
// if item is on bodypart, container will be NULL // if item is on bodypart, container will be NULL
@ -120,42 +188,46 @@ class D2Item {
} }
break; break;
} }
// if item is ear // if item is ear
if ($this->iData['ear']){ if ($this->iData['ear']) {
// set item code/basename // set item code/basename
$this->iData['itemCode'] = 'ear'; $this->iData['itemCode'] = 'ear';
$this->iData['basename'] = 'Ear'; $this->iData['basename'] = 'Ear';
// get ear class/level // get ear class/level
$eclass = bindec($b->readr(3)); // bit 76 $eclass = bindec($b->readr(3)); // bit 76
$elevel = bindec($b->readr(7)); // bit 79 $elevel = bindec($b->readr(7)); // bit 79
// get ear char's name // get ear char's name
} }
// get item code // get item code
$b->seek(76); $b->seek(76);
$itemCode = ''; $itemCode = '';
foreach (str_split($b->read(32), 8) as $byte) { foreach (str_split($b->read(32), 8) as $byte) {
$itemCode .= chr(bindec(strrev($byte))); $itemCode .= chr(bindec(strrev($byte)));
} }
$this->iData['itemCode'] = trim($itemCode); $this->iData['code'] = trim($itemCode);
// get namestr $sql = "
$sql = "SELECT code,namestr FROM armor WHERE code='{$this->iData['itemCode']}'"; SELECT code, namestr
FROM armor
WHERE code = '{$this->iData['code']}'
UNION
SELECT code, namestr
FROM misc
WHERE code = '{$this->iData['code']}'
UNION
SELECT code, namestr
FROM weapons
WHERE code = '{$this->iData['code']}'
";
$res = PDO_FetchAssoc($sql); $res = PDO_FetchAssoc($sql);
if (empty($res)){
$sql = "SELECT code,namestr FROM misc WHERE code='{$this->iData['itemCode']}'"; $sql = "SELECT `String` FROM strings WHERE `Key`='{$res[$this->iData['code']]}'";
$res = PDO_FetchAssoc($sql); $res = PDO_FetchOne($sql);
}
if (empty($res)){
$sql = "SELECT code,namestr FROM weapons WHERE code='{$this->iData['itemCode']}'";
$res = PDO_FetchAssoc($sql);
}
$sql = "SELECT `String` FROM strings WHERE `Key`='{$res[$this->iData['itemCode']]}'";
$res = PDO_FetchOne($sql);
$this->iData['basename'] = $res; $this->iData['basename'] = $res;
$this->iData['basename'] = preg_replace('/ÿc[0-9]/', '', $this->iData['basename']);
return $this->iData; return $this->iData;
} }

View File

@ -110,3 +110,14 @@ class D2ItemLocationBody {
const WEAPONL2 = 12; const WEAPONL2 = 12;
} }
class D2ItemQuality {
const LOW_QUALITY = 1;
const NORMAL = 2;
const HIGH_QUALITY = 3;
const MAGIC = 4;
const SET = 5;
const RARE = 6;
const UNIQUE = 7;
const CRAFTED = 8;
}

View File

@ -110,16 +110,16 @@ EOT;
// ddump($c->cData['CharacterStatusDied']); // ddump($c->cData['CharacterStatusDied']);
$statuses = [ $statuses = [
'Died' => 'CharacterStatusDied', 'Died' => 'CharacterStatusDied',
'Expansion' => 'CharacterStatusExpansion', 'Expansion' => 'CharacterStatusExpansion',
'Hardcore' => 'CharacterStatusHardcore', 'Hardcore' => 'CharacterStatusHardcore',
]; ];
$checkboxes = ''; $checkboxes = '';
foreach ($statuses as $status => $dataKey) { foreach ($statuses as $status => $dataKey) {
$checked = $c->cData[$dataKey] ? 'checked' : ''; $checked = $c->cData[$dataKey] ? 'checked' : '';
$checkboxes .= <<<HTML $checkboxes .= <<<HTML
<div class="col"> <div class="col">
<div class="form-check"> <div class="form-check">
<label class="form-check-label" for="{$status}">{$status}: </label><br> <label class="form-check-label" for="{$status}">{$status}: </label><br>
@ -129,6 +129,31 @@ foreach ($statuses as $status => $dataKey) {
HTML; HTML;
} }
$items = "<div class='container'><div class='row'>";
foreach($c->cData['items'] as $item) {
$i = array_filter($item->iData);
$items .= "<div style='border: 1px solid #ccc;' class='col-md-3'>
<p style='font-size: 120%;'>
{$i['basename']}<br>
<img src='/docs/{$_SESSION['modname']}/img/items/inv{$i['code']}.png'>
</p>
<ul>
<li>Code: {$i['code']}</li>
<li>Socketed: {$i['gems_in']}</li>
<li>Item Level{$i['ilvl']}</li>
<li>Identified: {$i['identified']}</li>
<li>Starting Item: {$i['startingItem']}</li>
<li>Ethereal: {$i['ethereal']}</li>
<li>Quality: {$i['iquality']}</li>
<li>Container: {$i['container']}</li>
<li>Runeword:{$i['runeword']}</li>
</ul>
</div>";
}
$items .= "</div></div>";
$tabContent .= <<<EOT $tabContent .= <<<EOT
<div style="background: white; margin-top: 10px;" class="tab-pane fade" id="{$c->cData['CharacterName']}" role="tabpanel" aria-labelledby="{$c->cData['CharacterName']}-tab"> <div style="background: white; margin-top: 10px;" class="tab-pane fade" id="{$c->cData['CharacterName']}" role="tabpanel" aria-labelledby="{$c->cData['CharacterName']}-tab">
@ -226,6 +251,10 @@ $checkboxes
$wps $wps
</div> </div>
</div> </div>
$items
</div> </div>
<!--<input class="btn btn-danger" style="" type="submit" value="Save Character">--> <!--<input class="btn btn-danger" style="" type="submit" value="Save Character">-->
</form> </form>