['j' => ['?_item_stats AS `is` ON `is`.`id` = `i`.`id`', true]],
's' => ['j' => ['?_spell AS `s` ON s.effect1CreateItemId = i.id', true], 'g' => 'i.id'],
'i' => [['is'], 'o' => 'i.quality DESC, i.itemLevel DESC']
);
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();
// readdress itemset .. is wrong for virtual sets
if ($miscData && isset($miscData['pcsToSet']) && isset($miscData['pcsToSet'][$this->id]))
$this->json[$this->id]['itemset'] = $miscData['pcsToSet'][$this->id];
// unify those pesky masks
$_ = &$_curTpl['requiredClass'];
if ($_ < 0 || ($_ & CLASS_MASK_ALL) == CLASS_MASK_ALL)
$_ = 0;
$_ = &$_curTpl['requiredRace'];
if ($_ < 0 || ($_ & RACE_MASK_ALL) == RACE_MASK_ALL)
$_ = 0;
}
}
// use if you JUST need the name
public static function getName($id)
{
$n = DB::Aowow()->selectRow('
SELECT
name_loc0, name_loc2, name_loc3, 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 = 0)
{
if (empty($this->vendors))
{
$ids = array_keys($this->templates);
$itemz = DB::Aowow()->select('
SELECT nv.item AS ARRAY_KEY1, nv.entry AS ARRAY_KEY2, 0 AS eventId, nv.maxcount, iec.* FROM npc_vendor nv LEFT JOIN ?_itemextendedcost iec ON nv.extendedCost = iec.id WHERE {nv.entry IN (?a) AND} nv.item IN (?a)
UNION
SELECT genv.item AS ARRAY_KEY1, c.id AS ARRAY_KEY2, genv.eventEntry AS eventId, genv.maxcount, iec.* FROM game_event_npc_vendor genv JOIN creature c ON c.guid = genv.guid LEFT JOIN ?_itemextendedcost iec ON genv.extendedCost = iec.id {JOIN creature c ON c.guid = genv.guid AND 1= ?d} WHERE {c.id IN (?a) AND} genv.item IN (?a)',
empty($filter[TYPE_NPC]) || !is_array($filter[TYPE_NPC]) ? DBSIMPLE_SKIP : $filter[TYPE_NPC],
$ids,
empty($filter[TYPE_NPC]) || !is_array($filter[TYPE_NPC]) ? DBSIMPLE_SKIP : 1,
empty($filter[TYPE_NPC]) || !is_array($filter[TYPE_NPC]) ? DBSIMPLE_SKIP : $filter[TYPE_NPC],
$ids
);
$cItems = [];
foreach ($itemz as $k => $vendors)
{
foreach ($vendors as $l => $costs)
{
$data = array(
'stock' => $costs['maxcount'] ? $costs['maxcount'] : -1,
'event' => $costs['eventId'],
'reqRtg' => $costs['reqPersonalRating']
);
if ($_ = $this->getField('buyPrice')) // somewhat nonsense.. is identical for all vendors (obviously)
$data[0] = $_;
// hardcode arena(103) & honor(104)
if ($_ = @$costs['reqArenaPoints'])
{
$data[-103] = $_;
$this->jsGlobals[TYPE_CURRENCY][103] = 103;
}
if ($_ = @$costs['reqHonorPoints'])
{
$data[-104] = $_;
$this->jsGlobals[TYPE_CURRENCY][104] = 104;
}
for ($i = 1; $i < 6; $i++)
{
if (($_ = @$costs['reqItemId'.$i]) && $costs['itemCount'.$i] > 0)
{
$data[$_] = $costs['itemCount'.$i];
$cItems[] = $_;
}
}
$vendors[$l] = $data;
}
$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 $id => $vendors)
{
foreach ($vendors as $l => $costs)
{
foreach ($costs as $k => $v)
{
if (in_array($k, $cItems))
{
$found = false;
foreach ($moneyItems->iterate() as $__)
{
if ($moneyItems->getField('itemId') == $k)
{
unset($costs[$k]);
$costs[-$moneyItems->id] = $v;
$found = true;
break;
}
}
if (!$found)
$this->jsGlobals[TYPE_ITEM][$k] = $k;
}
}
$vendors[$l] = $costs;
}
$itemz[$id] = $vendors;
}
}
$this->vendors = $itemz;
}
$result = $this->vendors;
// apply filter if given
$tok = @$filter[TYPE_ITEM];
$cur = @$filter[TYPE_CURRENCY];
foreach ($result as $itemId => &$data)
{
$reqRating = 0;
foreach ($data as $npcId => $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
// note: how to distinguish between brackets .. or team/pers-rating?
if (isset($data[$npcId]) && ($reqRating < $costs['reqRtg']))
$reqRating = $costs['reqRtg'];
}
if ($reqRating)
$data['reqRating'] = $reqRating;
if (empty($data))
unset($result[$itemId]);
}
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
*/
// random item is random
if ($addInfoMask & ITEMINFO_SUBITEMS)
$this->initSubItems();
if ($addInfoMask & ITEMINFO_JSON)
$this->extendJsonStats();
$data = [];
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 ($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 result; search for the right one
if ($cost = @reset($this->getExtendedCost($miscData)[$this->id]))
{
$currency = [];
$tokens = [];
foreach ($cost as $k => $qty)
{
if (is_string($k))
continue;
if ($k > 0)
$tokens[] = [$k, $qty];
else if ($k < 0)
$currency[] = [-$k, $qty];
}
$data[$this->id]['stock'] = $cost['stock']; // display as column in lv
$data[$this->id]['avail'] = $cost['stock']; // display as number on icon
$data[$this->id]['cost'] = [$this->getField('buyPrice')];
if ($e = $cost['event'])
{
$this->jsGlobals[TYPE_WORLDEVENT][$e] = $e;
$data[$this->id]['condition'] = array(
'type' => TYPE_WORLDEVENT,
'typeId' => -$e,
'status' => 1
);
}
if ($currency || $tokens) // fill idx:3 if required
$data[$this->id]['cost'][] = $currency;
if ($tokens)
$data[$this->id]['cost'][] = $tokens;
if ($_ = @$this->getExtendedCost($miscData)[$this->id]['reqRating'])
$data[$this->id]['reqarenartng'] = $_;
}
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'] = $_;
}
/* even more complicated crap
"source":[5],"sourcemore":[{"n":"Commander Oxheart","t":1,"ti":64606,"z":5842}],
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 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 ($rndEnch = DB::Aowow()->selectRow('SELECT * FROM ?_itemrandomenchant WHERE Id = ?d', $enhance['r']))
{
$_name .= ' '.Util::localizedString($rndEnch, 'name');
$randEnchant = '';
for ($i = 1; $i < 6; $i++)
{
if ($rndEnch['enchantId'.$i] <= 0)
continue;
$enchant = DB::Aowow()->selectRow('SELECT * FROM ?_itemenchantment WHERE Id = ?d', $rndEnch['enchantId'.$i]);
if ($rndEnch['allocationPct'.$i] > 0)
{
$amount = intVal($rndEnch['allocationPct'.$i] * $this->generateEnchSuffixFactor());
$randEnchant .= ''.str_replace('$i', $amount, Util::localizedString($enchant, 'text')).'
';
}
else
$randEnchant .= ''.Util::localizedString($enchant, 'text').'
';
}
}
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'] > 0) { $x .= ' '.Lang::$item['unique']; if ($this->curTpl['maxCount'] > 1) $x .= ' ('.$this->curTpl['maxCount'].')'; } else if ($_flags & ITEM_FLAG_UNIQUEEQUIPPED) $x .= ' '.Lang::$item['uniqueEquipped']; else if ($this->curTpl['itemLimitCategory']) { $limit = DB::Aowow()->selectRow("SELECT * FROM ?_itemlimitcategory WHERE id = ?", $this->curTpl['itemLimitCategory']); $x .= ' '.($limit['isGem'] ? Lang::$item['uniqueEquipped'] : Lang::$item['unique']).Lang::$main['colon'].Util::localizedString($limit, 'name').' ('.$limit['count'].')'; } // max duration if ($dur = $this->curTpl['duration']) $x .= " ".Lang::$game['duration'].Lang::$main['colon'].Util::formatTime(abs($dur) * 1000).($this->curTpl['flagsCustom'] & 0x1 ? ' ('.Lang::$item['realTime'].')' : null); // required holiday if ($hId = $this->curTpl['holidayId']) { $hDay = DB::Aowow()->selectRow("SELECT * FROM ?_holidays WHERE id = ?", $hId); $x .= ' '.sprintf(Lang::$game['requires'], ''.Util::localizedString($hDay, 'name').''); } // item begins a quest if ($this->curTpl['startQuest']) $x .= ' '.Lang::$item['startQuest'].''; // containerType (slotCount) if ($this->curTpl['slots'] > 0) { $fam = log($this->curTpl['bagFamily'], 2) + 1; // word order differs <_< if (in_array(User::$localeId, [LOCALE_FR, LOCALE_ES, LOCALE_RU])) $x .= ' '.sprintf(Lang::$item['bagSlotString'], Lang::$item['bagFamily'][$fam], $this->curTpl['slots']); else $x .= ' '.sprintf(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; $dmgmin1 = $this->curTpl['dmgMin1'] + $this->curTpl['dmgMin2']; $dmgmax1 = $this->curTpl['dmgMax1'] + $this->curTpl['dmgMax2']; $dps = $speed ? ($dmgmin1 + $dmgmax1) / (2 * $speed) : 0; if ($_class == ITEM_CLASS_AMMUNITION && $dmgmin1 && $dmgmax1) $x .= Lang::$item['addsDps'].' '.number_format(($dmgmin1 + $dmgmax1) / 2, 1).' '.Lang::$item['dps2'].' '; else if ($dps) { if ($_class == ITEM_CLASS_WEAPON) { $x .= '
'; // secondary damage is set if ($this->curTpl['dmgMin2']) $x .= '+'.sprintf($this->curTpl['dmgType2'] ? Lang::$item['damageMagic'] : Lang::$item['damagePhys'], $this->curTpl['dmgMin2'].' - '.$this->curTpl['dmgMax2'], Lang::$game['sc'][$this->curTpl['dmgType2']]).' '; if ($_class == ITEM_CLASS_WEAPON) $x .= '('.number_format($dps, 1).' '.Lang::$item['dps'].') '; // 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 .= ''.sprintf(Lang::$item['armor'], intVal($this->curTpl['armor'] + $this->curTpl['armorDamageModifier'])).' '; } else if (($this->curTpl['armor'] + $this->curTpl['armorDamageModifier']) > 0) $x .= ''.sprintf(Lang::$item['armor'], intVal($this->curTpl['armor'] + $this->curTpl['armorDamageModifier'])).' '; // Block if ($this->curTpl['block']) $x .= ''.sprintf(Lang::$item['block'], $this->curTpl['block']).' '; // 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, 'text').' '; // activation conditions for meta gems if ($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; switch ($gemCnd['comparator'.$i]) { case 2: // requires less '; break; case 3: // requires more '; break; } } } } } // Random Enchantment - if random enchantment is set, prepend stats from it if ($this->curTpl['randomEnchant'] && !isset($enhance['r'])) $x .= ''.Lang::$item['randEnchant'].' '; else if (isset($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 if ($type >= ITEM_MOD_AGILITY && $type <= ITEM_MOD_STAMINA) $x .= ''.($qty > 0 ? '+' : '-').abs($qty).' '.Lang::$item['statType'][$type].' '; else // rating with % for reqLevel $green[] = $this->parseRating($type, $qty, $interactive, $causesScaling); } // magic resistances foreach (Util::$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, 'text').' '; else { unset($enhance['e']); $x .= ''; } } else // enchantment placeholder $x .= ''; // Sockets w/ Gems if (!empty($enhance['g'])) { $gems = DB::Aowow()->select('SELECT i.id AS ARRAY_KEY, i.iconString, ae.*, i.gemColorMask AS colorMask FROM ?_items i JOIN ?_itemenchantment ae ON ae.id = i.gemEnchantmentId WHERE i.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], 'text') : 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], 'text') : 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', $this->curTpl['socketBonus']); $x .= ''.Lang::$item['socketBonus'].Lang::$main['colon'].Util::localizedString($sbonus, 'text').' '; } // durability if ($dur = $this->curTpl['durability']) $x .= Lang::$item['durability'].' '.$dur.' / '.$dur.' '; // required classes if ($classes = Lang::getClassString($this->curTpl['requiredClass'], $jsg, $__)) { foreach ($jsg as $js) if (empty($this->jsGlobals[TYPE_CLASS][$js])) $this->jsGlobals[TYPE_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_RACE][$js])) $this->jsGlobals[TYPE_RACE][$js] = $js; if ($races != Lang::$game['ra'][0]) // not "both", but display combinations like: troll, dwarf $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::$game['reqLevelHlm'], ' 1'.Lang::$game['valueDelim'].MAX_LEVEL.' ('.($interactive ? sprintf(Util::$changeLevelString, MAX_LEVEL) : ''.MAX_LEVEL).')').' '; else if ($_reqLvl > 1) $x .= sprintf(Lang::$game['reqLevel'], $_reqLvl).' '; // required arena team rating / personal rating / todo (low): sort out what kind of rating if (@$this->getExtendedCost([], $reqRating)[$this->id] && $reqRating) $x .= sprintf(Lang::$item['reqRating'], $reqRating).' '; // item level if (in_array($_class, [ITEM_CLASS_ARMOR, ITEM_CLASS_WEAPON])) $x .= 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'], true)) $x .= ''.Lang::$item['locked'].' '.implode(' ', $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 = []; $condition = ['OR', ['item1', $this->id], ['item2', $this->id], ['item3', $this->id], ['item4', $this->id], ['item5', $this->id], ['item6', $this->id], ['item7', $this->id], ['item8', $this->id], ['item9', $this->id], ['item10', $this->id]]; $itemset = new ItemsetList($condition); if (!$itemset->error) { $pieces = DB::Aowow()->select(' SELECT b.id AS ARRAY_KEY, b.name_loc0, b.name_loc2, b.name_loc3, 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 = ' '.$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 .= '('.$setSpells[$i]['bonus'].') '.Lang::$item['set'].': '.$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 (single charges not shown)) if ($this->curTpl['spellCharges1'] > 1 || $this->curTpl['spellCharges1'] < -1) $xMisc[] = ''.abs($this->curTpl['spellCharges1']).' '.Lang::$item['charges'].''; // 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 .= ' |