Type::ITEM
'i' => [['is', 'src', 'ic'], 'o' => 'i.quality DESC, i.itemLevel DESC'],
'ic' => ['j' => ['?_icons `ic` ON `ic`.`id` = `i`.`iconId`', true], 's' => ', ic.name AS iconString'],
'is' => ['j' => ['?_item_stats `is` ON `is`.`type` = 3 AND `is`.`typeId` = `i`.`id`', true], 's' => ', `is`.*'],
's' => ['j' => ['?_spell `s` ON `s`.`effect1CreateItemId` = `i`.`id`', true], 'g' => 'i.id'],
'e' => ['j' => ['?_events `e` ON `e`.`id` = `i`.`eventId`', true], 's' => ', e.holidayId'],
'src' => ['j' => ['?_source `src` ON `src`.`type` = 3 AND `src`.`typeId` = `i`.`id`', true], 's' => ', moreType, moreTypeId, src1, src2, src3, src4, src5, src6, src7, src8, src9, src10, src11, src12, src13, src14, src15, src16, src17, src18, src19, src20, src21, src22, src23, src24']
);
public function __construct($conditions = [], $miscData = null)
{
parent::__construct($conditions, $miscData);
foreach ($this->iterate() as &$_curTpl)
{
// item is scaling; overwrite other values
if ($_curTpl['scalingStatDistribution'] > 0 && $_curTpl['scalingStatValue'] > 0)
$this->initScalingStats();
$this->initJsonStats();
if ($miscData)
{
// readdress itemset .. is wrong for virtual sets
if (isset($miscData['pcsToSet']) && isset($miscData['pcsToSet'][$this->id]))
$this->json[$this->id]['itemset'] = $miscData['pcsToSet'][$this->id];
// additional rel attribute for listview rows
if (isset($miscData['extraOpts']['relEnchant']))
$this->relEnchant = $miscData['extraOpts']['relEnchant'];
}
// unify those pesky masks
$_ = &$_curTpl['requiredClass'];
$_ &= CLASS_MASK_ALL;
if ($_ < 0 || $_ == CLASS_MASK_ALL)
$_ = 0;
unset($_);
$_ = &$_curTpl['requiredRace'];
$_ &= RACE_MASK_ALL;
if ($_ < 0 || $_ == RACE_MASK_ALL)
$_ = 0;
unset($_);
// sources
for ($i = 1; $i < 25; $i++)
{
if ($_ = $_curTpl['src'.$i])
$this->sources[$this->id][$i][] = $_;
unset($_curTpl['src'.$i]);
}
}
}
// use if you JUST need the name
public static function getName($id)
{
$n = DB::Aowow()->selectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8 FROM ?_items WHERE id = ?d', $id);
return Util::localizedString($n, 'name');
}
// todo (med): information will get lost if one vendor sells one item multiple times with different costs (e.g. for item 54637)
// wowhead seems to have had the same issues
public function getExtendedCost($filter = [], &$reqRating = [])
{
if ($this->error)
return [];
$idx = $this->id;
if (empty($this->vendors))
{
$itemz = [];
$xCostData = [];
$rawEntries = DB::World()->select('
SELECT nv.item, nv.entry, 0 AS eventId, nv.maxcount, nv.extendedCost, nv.incrtime FROM npc_vendor nv WHERE {nv.entry IN (?a) AND} nv.item IN (?a)
UNION
SELECT genv.item, c.id AS `entry`, ge.eventEntry AS eventId, genv.maxcount, genv.extendedCost, genv.incrtime FROM game_event_npc_vendor genv LEFT JOIN game_event ge ON genv.eventEntry = ge.eventEntry JOIN creature c ON c.guid = genv.guid WHERE {c.id IN (?a) AND} genv.item IN (?a)',
empty($filter[Type::NPC]) || !is_array($filter[Type::NPC]) ? DBSIMPLE_SKIP : $filter[Type::NPC],
array_keys($this->templates),
empty($filter[Type::NPC]) || !is_array($filter[Type::NPC]) ? DBSIMPLE_SKIP : $filter[Type::NPC],
array_keys($this->templates)
);
foreach ($rawEntries as $costEntry)
{
if ($costEntry['extendedCost'])
$xCostData[] = $costEntry['extendedCost'];
if (!isset($itemz[$costEntry['item']][$costEntry['entry']]))
$itemz[$costEntry['item']][$costEntry['entry']] = [$costEntry];
else
$itemz[$costEntry['item']][$costEntry['entry']][] = $costEntry;
}
if ($xCostData)
$xCostData = DB::Aowow()->select('SELECT *, id AS ARRAY_KEY FROM ?_itemextendedcost WHERE id IN (?a)', $xCostData);
$cItems = [];
foreach ($itemz as $k => $vendors)
{
foreach ($vendors as $l => $vendor)
{
foreach ($vendor as $m => $vInfo)
{
$costs = [];
if (!empty($xCostData[$vInfo['extendedCost']]))
$costs = $xCostData[$vInfo['extendedCost']];
$data = array(
'stock' => $vInfo['maxcount'] ?: -1,
'event' => $vInfo['eventId'],
'restock' => $vInfo['incrtime'],
'reqRating' => $costs ? $costs['reqPersonalRating'] : 0,
'reqBracket' => $costs ? $costs['reqArenaSlot'] : 0
);
// hardcode arena(103) & honor(104)
if (!empty($costs['reqArenaPoints']))
{
$data[-103] = $costs['reqArenaPoints'];
$this->jsGlobals[Type::CURRENCY][103] = 103;
}
if (!empty($costs['reqHonorPoints']))
{
$data[-104] = $costs['reqHonorPoints'];
$this->jsGlobals[Type::CURRENCY][104] = 104;
}
for ($i = 1; $i < 6; $i++)
{
if (!empty($costs['reqItemId'.$i]) && $costs['itemCount'.$i] > 0)
{
$data[$costs['reqItemId'.$i]] = $costs['itemCount'.$i];
$cItems[] = $costs['reqItemId'.$i];
}
}
// no extended cost or additional gold required
if (!$costs || $this->getField('flagsExtra') & 0x04)
{
$this->getEntry($k);
if ($_ = $this->getField('buyPrice'))
$data[0] = $_;
}
$vendor[$m] = $data;
}
$vendors[$l] = $vendor;
}
$itemz[$k] = $vendors;
}
// convert items to currency if possible
if ($cItems)
{
$moneyItems = new CurrencyList(array(['itemId', $cItems]));
foreach ($moneyItems->getJSGlobals() as $type => $jsData)
foreach ($jsData as $k => $v)
$this->jsGlobals[$type][$k] = $v;
foreach ($itemz as $itemId => $vendors)
{
foreach ($vendors as $npcId => $costData)
{
foreach ($costData as $itr => $cost)
{
foreach ($cost as $k => $v)
{
if (in_array($k, $cItems))
{
$found = false;
foreach ($moneyItems->iterate() as $__)
{
if ($moneyItems->getField('itemId') == $k)
{
unset($cost[$k]);
$cost[-$moneyItems->id] = $v;
$found = true;
break;
}
}
if (!$found)
$this->jsGlobals[Type::ITEM][$k] = $k;
}
}
$costData[$itr] = $cost;
}
$vendors[$npcId] = $costData;
}
$itemz[$itemId] = $vendors;
}
}
$this->vendors = $itemz;
}
$result = $this->vendors;
// apply filter if given
$tok = !empty($filter[Type::ITEM]) ? $filter[Type::ITEM] : null;
$cur = !empty($filter[Type::CURRENCY]) ? $filter[Type::CURRENCY] : null;
foreach ($result as $itemId => &$data)
{
$reqRating = [];
foreach ($data as $npcId => $entries)
{
foreach ($entries as $costs)
{
if ($tok || $cur) // bought with specific token or currency
{
$valid = false;
foreach ($costs as $k => $qty)
{
if ((!$tok || $k == $tok) && (!$cur || $k == -$cur))
{
$valid = true;
break;
}
}
if (!$valid)
unset($data[$npcId]);
}
// reqRating ins't really a cost .. so pass it by ref instead of return
// use highest total value
if (isset($data[$npcId]) && $costs['reqRating'] && (!$reqRating || $reqRating[0] < $costs['reqRating']))
$reqRating = [$costs['reqRating'], $costs['reqBracket']];
}
}
if (empty($data))
unset($result[$itemId]);
}
// restore internal index;
$this->getEntry($idx);
return $result;
}
public function getListviewData($addInfoMask = 0x0, $miscData = null)
{
/*
* ITEMINFO_JSON (0x01): itemMods (including spells) and subitems parsed
* ITEMINFO_SUBITEMS (0x02): searched by comparison
* ITEMINFO_VENDOR (0x04): costs-obj, when displayed as vendor
* ITEMINFO_GEM (0x10): gem infos and score
* ITEMINFO_MODEL (0x20): sameModelAs-Tab
*/
$data = [];
// random item is random
if ($addInfoMask & ITEMINFO_SUBITEMS)
$this->initSubItems();
if ($addInfoMask & ITEMINFO_JSON)
$this->extendJsonStats();
$extCosts = [];
if ($addInfoMask & ITEMINFO_VENDOR)
$extCosts = $this->getExtendedCost($miscData);
$extCostOther = [];
foreach ($this->iterate() as $__)
{
foreach ($this->json[$this->id] as $k => $v)
$data[$this->id][$k] = $v;
// json vs listview quirk
$data[$this->id]['name'] = $data[$this->id]['quality'].$data[$this->id]['name'];
unset($data[$this->id]['quality']);
if (!empty($this->relEnchant) && $this->curTpl['randomEnchant'])
{
if (($x = array_search($this->curTpl['randomEnchant'], array_column($this->relEnchant, 'entry'))) !== false)
{
$data[$this->id]['rel'] = 'rand='.$this->relEnchant[$x]['ench'];
$data[$this->id]['name'] .= ' '.$this->relEnchant[$x]['name'];
}
}
if ($addInfoMask & ITEMINFO_JSON)
{
foreach ($this->itemMods[$this->id] as $k => $v)
$data[$this->id][$k] = $v;
if ($_ = intVal(($this->curTpl['minMoneyLoot'] + $this->curTpl['maxMoneyLoot']) / 2))
$data[$this->id]['avgmoney'] = $_;
if ($_ = $this->curTpl['repairPrice'])
$data[$this->id]['repaircost'] = $_;
}
if ($addInfoMask & (ITEMINFO_JSON | ITEMINFO_GEM))
if (isset($this->curTpl['score']))
$data[$this->id]['score'] = $this->curTpl['score'];
if ($addInfoMask & ITEMINFO_GEM)
{
$data[$this->id]['uniqEquip'] = ($this->curTpl['flags'] & ITEM_FLAG_UNIQUEEQUIPPED) ? 1 : 0;
$data[$this->id]['socketLevel'] = 0; // not used with wotlk
}
if ($addInfoMask & ITEMINFO_VENDOR)
{
// just use the first results
// todo (med): dont use first vendor; search for the right one
if (!empty($extCosts[$this->id]))
{
$cost = reset($extCosts[$this->id]);
foreach ($cost as $itr => $entries)
{
$currency = [];
$tokens = [];
$costArr = [];
foreach ($entries as $k => $qty)
{
if (is_string($k))
continue;
if ($k > 0)
$tokens[] = [$k, $qty];
else if ($k < 0)
$currency[] = [-$k, $qty];
}
$costArr['stock'] = $entries['stock'];// display as column in lv
$costArr['avail'] = $entries['stock'];// display as number on icon
$costArr['cost'] = [empty($entries[0]) ? 0 : $entries[0]];
$costArr['restock'] = $entries['restock'];
if ($entries['event'])
{
$this->jsGlobals[Type::WORLDEVENT][$entries['event']] = $entries['event'];
$costArr['condition'][0][$this->id][] = [[CND_ACTIVE_EVENT, $entries['event']]];
}
if ($currency || $tokens) // fill idx:3 if required
$costArr['cost'][] = $currency;
if ($tokens)
$costArr['cost'][] = $tokens;
if (!empty($entries['reqRating']))
$costArr['reqarenartng'] = $entries['reqRating'];
if ($itr > 0)
$extCostOther[$this->id][] = $costArr;
else
$data[$this->id] = array_merge($data[$this->id], $costArr);
}
}
if ($x = $this->curTpl['buyPrice'])
$data[$this->id]['buyprice'] = $x;
if ($x = $this->curTpl['sellPrice'])
$data[$this->id]['sellprice'] = $x;
if ($x = $this->curTpl['buyCount'])
$data[$this->id]['stack'] = $x;
}
if ($this->curTpl['class'] == ITEM_CLASS_GLYPH)
$data[$this->id]['glyph'] = $this->curTpl['subSubClass'];
if ($x = $this->curTpl['requiredSkill'])
$data[$this->id]['reqskill'] = $x;
if ($x = $this->curTpl['requiredSkillRank'])
$data[$this->id]['reqskillrank'] = $x;
if ($x = $this->curTpl['requiredSpell'])
$data[$this->id]['reqspell'] = $x;
if ($x = $this->curTpl['requiredFaction'])
$data[$this->id]['reqfaction'] = $x;
if ($x = $this->curTpl['requiredFactionRank'])
{
$data[$this->id]['reqrep'] = $x;
$data[$this->id]['standing'] = $x; // used in /faction item-listing
}
if ($x = $this->curTpl['slots'])
$data[$this->id]['nslots'] = $x;
$_ = $this->curTpl['requiredRace'];
if ($_ && $_ & RACE_MASK_ALLIANCE != RACE_MASK_ALLIANCE && $_ & RACE_MASK_HORDE != RACE_MASK_HORDE)
$data[$this->id]['reqrace'] = $_;
if ($_ = $this->curTpl['requiredClass'])
$data[$this->id]['reqclass'] = $_; // $data[$this->id]['classes'] ??
if ($this->curTpl['flags'] & ITEM_FLAG_HEROIC)
$data[$this->id]['heroic'] = true;
if ($addInfoMask & ITEMINFO_MODEL)
if ($_ = $this->getField('displayId'))
$data[$this->id]['displayid'] = $_;
if ($this->getSources($s, $sm) && !($addInfoMask & ITEMINFO_MODEL))
{
$data[$this->id]['source'] = $s;
if ($sm)
$data[$this->id]['sourcemore'] = $sm;
}
if (!empty($this->curTpl['cooldown']))
$data[$this->id]['cooldown'] = $this->curTpl['cooldown'] / 1000;
}
foreach ($extCostOther as $itemId => $duplicates)
foreach ($duplicates as $d)
$data[] = array_merge($data[$itemId], $d); // we dont really use keys on data, but this may cause errors in future
/* even more complicated crap
modelviewer {type:X, displayid:Y, slot:z} .. not sure, when to set
*/
return $data;
}
public function getJSGlobals($addMask = GLOBALINFO_SELF, &$extra = [])
{
$data = $addMask & GLOBALINFO_RELATED ? $this->jsGlobals : [];
foreach ($this->iterate() as $id => $__)
{
if ($addMask & GLOBALINFO_SELF)
{
$data[Type::ITEM][$id] = array(
'name' => $this->getField('name', true),
'quality' => $this->curTpl['quality'],
'icon' => $this->curTpl['iconString']
);
}
if ($addMask & GLOBALINFO_EXTRA)
{
$extra[$id] = array(
'id' => $id,
'tooltip' => $this->renderTooltip(true),
'spells' => new StdClass // placeholder for knownSpells
);
}
}
return $data;
}
/*
enhance (set by comparison tool or formated external links)
ench: enchantmentId
sock: bool (extraScoket (gloves, belt))
gems: array (:-separated itemIds)
rand: >0: randomPropId; <0: randomSuffixId
interactive (set to place javascript/anchors to manipulate level and ratings or link to filters (static tooltips vs popup tooltip))
subOf (tabled layout doesn't work if used as sub-tooltip in other item or spell tooltips; use line-break instead)
*/
public function getField($field, $localized = false, $silent = false, $enhance = [])
{
$res = parent::getField($field, $localized, $silent);
if ($field == 'name' && !empty($enhance['r']))
if ($this->getRandEnchantForItem($enhance['r']))
$res .= ' '.Util::localizedString($this->enhanceR, 'name');
return $res;
}
public function renderTooltip($interactive = false, $subOf = 0, $enhance = [])
{
if ($this->error)
return;
$_name = $this->getField('name', true);
$_reqLvl = $this->curTpl['requiredLevel'];
$_quality = $this->curTpl['quality'];
$_flags = $this->curTpl['flags'];
$_class = $this->curTpl['class'];
$_subClass = $this->curTpl['subClass'];
$_slot = $this->curTpl['slot'];
$causesScaling = false;
if (!empty($enhance['r']))
{
if ($this->getRandEnchantForItem($enhance['r']))
{
$_name .= ' '.Util::localizedString($this->enhanceR, 'name');
$randEnchant = '';
for ($i = 1; $i < 6; $i++)
{
if ($this->enhanceR['enchantId'.$i] <= 0)
continue;
$enchant = DB::Aowow()->selectRow('SELECT * FROM ?_itemenchantment WHERE id = ?d', $this->enhanceR['enchantId'.$i]);
if ($this->enhanceR['allocationPct'.$i] > 0)
{
$amount = intVal($this->enhanceR['allocationPct'.$i] * $this->generateEnchSuffixFactor());
$randEnchant .= ''.str_replace('$i', $amount, Util::localizedString($enchant, 'name')).'
';
}
else
$randEnchant .= ''.Util::localizedString($enchant, 'name').'
';
}
}
else
unset($enhance['r']);
}
if (isset($enhance['s']) && !in_array($_slot, [INVTYPE_WRISTS, INVTYPE_WAIST, INVTYPE_HANDS]))
unset($enhance['s']);
// IMPORTAT: DO NOT REMOVE THE HTML-COMMENTS! THEY ARE REQUIRED TO UPDATE THE TOOLTIP CLIENTSIDE
$x = '';
// upper table: stats
if (!$subOf)
$x .= '
| ';
// name; quality
if ($subOf)
$x .= ''.$_name.'';
else
$x .= ''.$_name.'';
// heroic tag
if (($_flags & ITEM_FLAG_HEROIC) && $_quality == ITEM_QUALITY_EPIC)
$x .= ' '.Lang::item('heroic').''; // requires map (todo: reparse ?_zones for non-conflicting data; generate Link to zone) if ($_ = $this->curTpl['map']) { $map = DB::Aowow()->selectRow('SELECT * FROM ?_zones WHERE mapId = ?d LIMIT 1', $_); $x .= ' '.Util::localizedString($map, 'name').''; } // requires area if ($this->curTpl['area']) { $area = DB::Aowow()->selectRow('SELECT * FROM ?_zones WHERE Id=?d LIMIT 1', $this->curTpl['area']); $x .= ' '.Util::localizedString($area, 'name'); } // conjured if ($_flags & ITEM_FLAG_CONJURED) $x .= ' '.Lang::item('conjured'); // bonding if ($_flags & ITEM_FLAG_ACCOUNTBOUND) $x .= ' '.Lang::item('bonding', 0); else if ($this->curTpl['bonding']) $x .= ' '.Lang::item('bonding', $this->curTpl['bonding']); // unique || unique-equipped || unique-limited if ($this->curTpl['maxCount'] == 1) $x .= ' '.Lang::item('unique', 0); // not for currency tokens else if ($this->curTpl['maxCount'] && $this->curTpl['bagFamily'] != 8192) $x .= ' '.sprintf(Lang::item('unique', 1), $this->curTpl['maxCount']); else if ($_flags & ITEM_FLAG_UNIQUEEQUIPPED) $x .= ' '.Lang::item('uniqueEquipped', 0); else if ($this->curTpl['itemLimitCategory']) { $limit = DB::Aowow()->selectRow("SELECT * FROM ?_itemlimitcategory WHERE id = ?", $this->curTpl['itemLimitCategory']); $x .= ' '.sprintf(Lang::item($limit['isGem'] ? 'uniqueEquipped' : 'unique', 2), Util::localizedString($limit, 'name'), $limit['count']); } // required holiday if ($eId = $this->curTpl['eventId']) if ($hName = DB::Aowow()->selectRow('SELECT h.* FROM ?_holidays h JOIN ?_events e ON e.holidayId = h.id WHERE e.id = ?d', $eId)) $x .= ' '.sprintf(Lang::game('requires'), ''.Util::localizedString($hName, 'name').''); // item begins a quest if ($this->curTpl['startQuest']) $x .= ' '.Lang::item('startQuest').''; // containerType (slotCount) if ($this->curTpl['slots'] > 0) { $fam = $this->curTpl['bagFamily'] ? log($this->curTpl['bagFamily'], 2) + 1 : 0; $x .= ' '.Lang::item('bagSlotString', [$this->curTpl['slots'], Lang::item('bagFamily', $fam)]); } if (in_array($_class, [ITEM_CLASS_ARMOR, ITEM_CLASS_WEAPON, ITEM_CLASS_AMMUNITION])) { $x .= '
'.Lang::item('inventoryType', $_slot).' '; else $x .= ' '; // Weapon/Ammunition Stats (not limited to weapons (see item:1700)) $speed = $this->curTpl['delay'] / 1000; $sc1 = $this->curTpl['dmgType1']; $sc2 = $this->curTpl['dmgType2']; $dmgmin = $this->curTpl['dmgMin1'] + $this->curTpl['dmgMin2']; $dmgmax = $this->curTpl['dmgMax1'] + $this->curTpl['dmgMax2']; $dps = $speed ? ($dmgmin + $dmgmax) / (2 * $speed) : 0; if ($_class == ITEM_CLASS_AMMUNITION && $dmgmin && $dmgmax) { if ($sc1) $x .= sprintf(Lang::item('damage', 'ammo', 1), ($dmgmin + $dmgmax) / 2, Lang::game('sc', $sc1)).' '; else $x .= sprintf(Lang::item('damage', 'ammo', 0), ($dmgmin + $dmgmax) / 2).' '; } else if ($dps) { if ($this->curTpl['dmgMin1'] == $this->curTpl['dmgMax1']) $dmg = sprintf(Lang::item('damage', 'single', $sc1 ? 1 : 0), $this->curTpl['dmgMin1'], $sc1 ? Lang::game('sc', $sc1) : null); else $dmg = sprintf(Lang::item('damage', 'range', $sc1 ? 1 : 0), $this->curTpl['dmgMin1'], $this->curTpl['dmgMax1'], $sc1 ? Lang::game('sc', $sc1) : null); if ($_class == ITEM_CLASS_WEAPON) // do not use localized format here! $x .= '
'; // secondary damage is set if (($this->curTpl['dmgMin2'] || $this->curTpl['dmgMax2']) && $this->curTpl['dmgMin2'] != $this->curTpl['dmgMax2']) $x .= sprintf(Lang::item('damage', 'range', $sc2 ? 3 : 2), $this->curTpl['dmgMin2'], $this->curTpl['dmgMax2'], $sc2 ? Lang::game('sc', $sc2) : null).' '; else if ($this->curTpl['dmgMin2']) $x .= sprintf(Lang::item('damage', 'single', $sc2 ? 3 : 2), $this->curTpl['dmgMin2'], $sc2 ? Lang::game('sc', $sc2) : null).' '; if ($_class == ITEM_CLASS_WEAPON) $x .= ''.sprintf(Lang::item('dps'), $dps).' '; // do not use localized format here! // display FeralAttackPower if set if ($fap = $this->getFeralAP()) $x .= '('.$fap.' '.Lang::item('fap').') '; } // Armor if ($_class == ITEM_CLASS_ARMOR && $this->curTpl['armorDamageModifier'] > 0) { $spanI = 'class="q2"'; if ($interactive) $spanI = 'class="q2 tip" onmouseover="$WH.Tooltip.showAtCursor(event, $WH.sprintf(LANG.tooltip_armorbonus, '.$this->curTpl['armorDamageModifier'].'), 0, 0, \'q\')" onmousemove="$WH.Tooltip.cursorUpdate(event)" onmouseout="$WH.Tooltip.hide()"'; $x .= ''.Lang::item('armor', [$this->curTpl['armor']]).' '; } else if ($this->curTpl['armor']) $x .= ''.Lang::item('armor', [$this->curTpl['armor']]).' '; // Block (note: block value from field block and from field stats or parsed from itemSpells are displayed independently) if ($this->curTpl['tplBlock']) $x .= ''.sprintf(Lang::item('block'), $this->curTpl['tplBlock']).' '; // Item is a gem (don't mix with sockets) if ($geId = $this->curTpl['gemEnchantmentId']) { $gemEnch = DB::Aowow()->selectRow('SELECT * FROM ?_itemenchantment WHERE id = ?d', $geId); $x .= ''.Util::localizedString($gemEnch, 'name').' '; // activation conditions for meta gems if (!empty($gemEnch['conditionId'])) { if ($gemCnd = DB::Aowow()->selectRow('SELECT * FROM ?_itemenchantmentcondition WHERE id = ?d', $gemEnch['conditionId'])) { for ($i = 1; $i < 6; $i++) { if (!$gemCnd['color'.$i]) continue; $vspfArgs = []; switch ($gemCnd['comparator'.$i]) { case 2: // requires less '; } } } } // Random Enchantment - if random enchantment is set, prepend stats from it if ($this->curTpl['randomEnchant'] && empty($enhance['r'])) $x .= ''.Lang::item('randEnchant').' '; else if (!empty($enhance['r'])) $x .= $randEnchant; // itemMods (display stats and save ratings for later use) for ($j = 1; $j <= 10; $j++) { $type = $this->curTpl['statType'.$j]; $qty = $this->curTpl['statValue'.$j]; if (!$qty || $type <= 0) continue; // base stat switch ($type) { case ITEM_MOD_MANA: case ITEM_MOD_HEALTH: // $type += 1; // i think i fucked up somewhere mapping item_mods: offsets may be required somewhere case ITEM_MOD_AGILITY: case ITEM_MOD_STRENGTH: case ITEM_MOD_INTELLECT: case ITEM_MOD_SPIRIT: case ITEM_MOD_STAMINA: $x .= ''.($qty > 0 ? '+' : '-').abs($qty).' '.Lang::item('statType', $type).' '; break; default: // rating with % for reqLevel $green[] = $this->parseRating($type, $qty, $interactive, $causesScaling); } } // magic resistances foreach (Game::$resistanceFields as $j => $rowName) if ($rowName && $this->curTpl[$rowName] != 0) $x .= '+'.$this->curTpl[$rowName].' '.Lang::game('resistances', $j).' '; // Enchantment if (isset($enhance['e'])) { if ($enchText = DB::Aowow()->selectRow('SELECT * FROM ?_itemenchantment WHERE id = ?', $enhance['e'])) $x .= ''.Util::localizedString($enchText, 'name').' '; else { unset($enhance['e']); $x .= ''; } } else // enchantment placeholder $x .= ''; // Sockets w/ Gems if (!empty($enhance['g'])) { $gems = DB::Aowow()->select(' SELECT it.id AS ARRAY_KEY, ic.name AS iconString, ae.*, it.gemColorMask AS colorMask FROM ?_items it JOIN ?_itemenchantment ae ON ae.id = it.gemEnchantmentId JOIN ?_icons ic ON ic.id = it.iconId WHERE it.id IN (?a)', $enhance['g']); foreach ($enhance['g'] as $k => $v) if ($v && !in_array($v, array_keys($gems))) // 0 is valid unset($enhance['g'][$k]); } else $enhance['g'] = []; // zero fill empty sockets $sockCount = isset($enhance['s']) ? 1 : 0; if (!empty($this->json[$this->id]['nsockets'])) $sockCount += $this->json[$this->id]['nsockets']; while ($sockCount > count($enhance['g'])) $enhance['g'][] = 0; $enhance['g'] = array_reverse($enhance['g']); $hasMatch = 1; // fill native sockets for ($j = 1; $j <= 3; $j++) { if (!$this->curTpl['socketColor'.$j]) continue; for ($i = 0; $i < 4; $i++) if (($this->curTpl['socketColor'.$j] & (1 << $i))) $colorId = $i; $pop = array_pop($enhance['g']); $col = $pop ? 1 : 0; $hasMatch &= $pop ? (($gems[$pop]['colorMask'] & (1 << $colorId)) ? 1 : 0) : 0; $icon = $pop ? sprintf(Util::$bgImagePath['tiny'], STATIC_URL, strtolower($gems[$pop]['iconString'])) : null; $text = $pop ? Util::localizedString($gems[$pop], 'name') : Lang::item('socket', $colorId); if ($interactive) $x .= ''.$text.' '; else $x .= ''.$text.' '; } // fill extra socket if (isset($enhance['s'])) { $pop = array_pop($enhance['g']); $col = $pop ? 1 : 0; $icon = $pop ? sprintf(Util::$bgImagePath['tiny'], STATIC_URL, strtolower($gems[$pop]['iconString'])) : null; $text = $pop ? Util::localizedString($gems[$pop], 'name') : Lang::item('socket', -1); if ($interactive) $x .= ''.$text.' '; else $x .= ''.$text.' '; } else // prismatic socket placeholder $x .= ''; if ($_ = $this->curTpl['socketBonus']) { $sbonus = DB::Aowow()->selectRow('SELECT * FROM ?_itemenchantment WHERE id = ?d', $_); $x .= ''.Lang::item('socketBonus', [''.Util::localizedString($sbonus, 'name').'']).' '; } // durability if ($dur = $this->curTpl['durability']) $x .= sprintf(Lang::item('durability'), $dur, $dur).' '; // max duration if ($dur = $this->curTpl['duration']) { $rt = ''; if ($this->curTpl['flagsCustom'] & 0x1) $rt = $interactive ? ' ('.sprintf(Util::$dfnString, 'LANG.tooltip_realduration', Lang::item('realTime')).')' : ' ('.Lang::item('realTime').')'; $x .= Lang::formatTime(abs($dur) * 1000, 'item', 'duration').$rt." "; } $jsg = []; // required classes if ($classes = Lang::getClassString($this->curTpl['requiredClass'], $jsg)) { foreach ($jsg as $js) if (empty($this->jsGlobals[Type::CHR_CLASS][$js])) $this->jsGlobals[Type::CHR_CLASS][$js] = $js; $x .= Lang::game('classes').Lang::main('colon').$classes.' '; } // required races if ($races = Lang::getRaceString($this->curTpl['requiredRace'], $jsg)) { foreach ($jsg as $js) if (empty($this->jsGlobals[Type::CHR_RACE][$js])) $this->jsGlobals[Type::CHR_RACE][$js] = $js; $x .= Lang::game('races').Lang::main('colon').$races.' '; } // required honorRank (not used anymore) if ($rhr = $this->curTpl['requiredHonorRank']) $x .= sprintf(Lang::game('requires'), Lang::game('pvpRank', $rhr)).' '; // required CityRank..? // what the f.. // required level if (($_flags & ITEM_FLAG_ACCOUNTBOUND) && $_quality == ITEM_QUALITY_HEIRLOOM) $x .= sprintf(Lang::item('reqLevelRange'), 1, MAX_LEVEL, ($interactive ? sprintf(Util::$changeLevelString, MAX_LEVEL) : ''.MAX_LEVEL)).' '; else if ($_reqLvl > 1) $x .= sprintf(Lang::item('reqMinLevel'), $_reqLvl).' '; // required arena team rating / personal rating / todo (low): sort out what kind of rating if (!empty($this->getExtendedCost([], $reqRating)[$this->id]) && $reqRating) $x .= sprintf(Lang::item('reqRating', $reqRating[1]), $reqRating[0]).' '; // item level if (in_array($_class, [ITEM_CLASS_ARMOR, ITEM_CLASS_WEAPON])) $x .= sprintf(Lang::item('itemLevel'), $this->curTpl['itemLevel']).' '; // required skill if ($reqSkill = $this->curTpl['requiredSkill']) { $_ = ''.SkillList::getName($reqSkill).''; if ($this->curTpl['requiredSkillRank'] > 0) $_ .= ' ('.$this->curTpl['requiredSkillRank'].')'; $x .= sprintf(Lang::game('requires'), $_).' '; } // required spell if ($reqSpell = $this->curTpl['requiredSpell']) $x .= Lang::game('requires2').' '.SpellList::getName($reqSpell).' '; // required reputation w/ faction if ($reqFac = $this->curTpl['requiredFaction']) $x .= sprintf(Lang::game('requires'), ''.FactionList::getName($reqFac).' - '.Lang::game('rep', $this->curTpl['requiredFactionRank'])).' '; // locked or openable if ($locks = Lang::getLocks($this->curTpl['lockId'], $arr, true, true)) $x .= ''.Lang::item('locked').' '.implode(' ', array_map(function($x) { return sprintf(Lang::game('requires'), $x); }, $locks)).' '; else if ($this->curTpl['flags'] & ITEM_FLAG_OPENABLE) $x .= ''.Lang::item('openClick').' '; // upper table: done if (!$subOf) $x .= ' |
| ';
if (isset($green))
foreach ($green as $j => $bonus)
if ($bonus)
$x .= ''.$bonus.' '; // Item Set $pieces = []; if ($setId = $this->getField('itemset')) { $condition = [ ['refSetId', $setId], // ['quality', $this->curTpl['quality']], ['minLevel', $this->curTpl['itemLevel'], '<='], ['maxLevel', $this->curTpl['itemLevel'], '>='] ]; $itemset = new ItemsetList($condition); if (!$itemset->error && $itemset->pieceToSet) { // handle special cases where: // > itemset has items of different qualities (handled by not limiting for this in the initial query) // > itemset is virtual and multiple instances have the same itemLevel but not quality (filter below) if ($itemset->getMatches() > 1) { foreach ($itemset->iterate() as $id => $__) { if ($itemset->getField('quality') == $this->curTpl['quality']) { $itemset->pieceToSet = array_filter($itemset->pieceToSet, function($x) use ($id) { return $id == $x; }); break; } } } $pieces = DB::Aowow()->select(' SELECT b.id AS ARRAY_KEY, b.name_loc0, b.name_loc2, b.name_loc3, b.name_loc4, b.name_loc6, b.name_loc8, GROUP_CONCAT(a.id SEPARATOR \':\') AS equiv FROM ?_items a, ?_items b WHERE a.slotBak = b.slotBak AND a.itemset = b.itemset AND b.id IN (?a) GROUP BY b.id;', array_keys($itemset->pieceToSet) ); foreach ($pieces as $k => &$p) $p = ''.Util::localizedString($p, 'name').''; $xSet = ' '.Lang::item('setName', [''.$itemset->getField('name', true).'', 0, count($pieces)]).''; if ($skId = $itemset->getField('skillId')) // bonus requires skill to activate { $xSet .= ' '.sprintf(Lang::game('requires'), ''.SkillList::getName($skId).''); if ($_ = $itemset->getField('skillLevel')) $xSet .= ' ('.$_.')'; $xSet .= ' '; } // list pieces $xSet .= ' '.implode(' ', $pieces).' '; // get bonuses $setSpellsAndIdx = []; for ($j = 1; $j <= 8; $j++) if ($_ = $itemset->getField('spell'.$j)) $setSpellsAndIdx[$_] = $j; $setSpells = []; if ($setSpellsAndIdx) { $boni = new SpellList(array(['s.id', array_keys($setSpellsAndIdx)])); foreach ($boni->iterate() as $__) { $setSpells[] = array( 'tooltip' => $boni->parseText('description', $_reqLvl > 1 ? $_reqLvl : MAX_LEVEL, false, $causesScaling)[0], 'entry' => $itemset->getField('spell'.$setSpellsAndIdx[$boni->id]), 'bonus' => $itemset->getField('bonus'.$setSpellsAndIdx[$boni->id]) ); } } // sort and list bonuses $xSet .= ''; for ($i = 0; $i < count($setSpells); $i++) { for ($j = $i; $j < count($setSpells); $j++) { if ($setSpells[$j]['bonus'] >= $setSpells[$i]['bonus']) continue; $tmp = $setSpells[$i]; $setSpells[$i] = $setSpells[$j]; $setSpells[$j] = $tmp; } $xSet .= ''.Lang::item('setBonus', [$setSpells[$i]['bonus'], ''.$setSpells[$i]['tooltip'].'']).''; if ($i < count($setSpells) - 1) $xSet .= ' '; } $xSet .= ''; } } // recipes, vanity pets, mounts if ($this->canTeachSpell()) { $craftSpell = new SpellList(array(['s.id', intVal($this->curTpl['spellId2'])])); if (!$craftSpell->error) { $xCraft = ''; if ($desc = $this->getField('description', true)) $x .= ''.Lang::item('trigger', 0).' '.$desc.' '; // recipe handling (some stray Techniques have subclass == 0), place at bottom of tooltipp if ($_class == ITEM_CLASS_RECIPE || $this->curTpl['bagFamily'] == 16) { $craftItem = new ItemList(array(['i.id', (int)$craftSpell->curTpl['effect1CreateItemId']])); if (!$craftItem->error) { if ($itemTT = $craftItem->renderTooltip($interactive, $this->id)) $xCraft .= ' '.$itemTT.' '.Lang::game('requires2').' '.implode(', ', $reqReag).' better) $xMisc = []; // itemset: pieces and boni if (isset($xSet)) $xMisc[] = $xSet; // funny, yellow text at the bottom, omit if we have a recipe if ($this->curTpl['description_loc0'] && !$this->canTeachSpell()) $xMisc[] = '"'.$this->getField('description', true).'"'; // readable if ($this->curTpl['pageTextId']) $xMisc[] = ''.Lang::item('readClick').''; // charges (i guess checking first spell is enough) if ($this->curTpl['spellCharges1']) $xMisc[] = ''.Lang::item('charges', [abs($this->curTpl['spellCharges1'])]).''; // list required reagents if (isset($xCraft)) $xMisc[] = $xCraft; if ($xMisc) $x .= implode(' ', $xMisc); if ($sp = $this->curTpl['sellPrice']) $x .= ' '.Lang::item('sellPrice').Lang::main('colon').Util::formatMoney($sp).' ';
if (!$subOf)
$x .= ' |