Files
aowow/includes/loot.class.php
Sarjuuk be7a84a651 Core/Conditions
* minor rework
 * fixed columns of tab item loot
 * fixed lookup of classes/races (as they are the only type used as bitmask)
 * implemented reverse lookups everywhere (arguably class, race and skill are too spammy)
 * reverse lookups no longer contain redundant data
 * changed how the groupKey is set, so there are no more cases that can't be looked up
 * fixes #273

 * title: added tab 'criteria-of'
2024-07-06 04:03:56 +02:00

691 lines
31 KiB
PHP

<?php
if (!defined('AOWOW_REVISION'))
die('illegal access');
/* from TC wiki
fishing_loot_template no relation entry is linked with ID of the fishing zone or area
creature_loot_template entry many <- many creature_template lootid
gameobject_loot_template entry many <- many gameobject_template data1 Only GO type 3 (CHEST) or 25 (FISHINGHOLE)
item_loot_template entry many <- one item_template entry
disenchant_loot_template entry many <- many item_template DisenchantID
prospecting_loot_template entry many <- one item_template entry
milling_loot_template entry many <- one item_template entry
pickpocketing_loot_template entry many <- many creature_template pickpocketloot
skinning_loot_template entry many <- many creature_template skinloot Can also store minable/herbable items gathered from creatures
quest_mail_loot_template entry quest_template RewMailTemplateId
reference_loot_template entry many <- many *_loot_template reference
*/
class Loot
{
public $jsGlobals = [];
public $extraCols = [];
private $entry = 0; // depending on the lookup itemId oder templateId
private $results = [];
private $chanceMods = [];
private $lootTemplates = array(
LOOT_REFERENCE, // internal
LOOT_ITEM, // item
LOOT_DISENCHANT, // item
LOOT_PROSPECTING, // item
LOOT_MILLING, // item
LOOT_CREATURE, // npc
LOOT_PICKPOCKET, // npc
LOOT_SKINNING, // npc (see its flags for mining, herbing, salvaging or actual skinning)
LOOT_FISHING, // zone
LOOT_GAMEOBJECT, // object (see its lockType for mining, herbing, fishing or generic looting)
LOOT_MAIL, // quest + achievement
LOOT_SPELL // spell
);
public function &iterate() : iterable
{
reset($this->results);
foreach ($this->results as $k => $__)
yield $k => $this->results[$k];
}
public function getResult() : array
{
return $this->results;
}
private function createStack(array $l) : string // issue: TC always has an equal distribution between min/max
{
if (empty($l['min']) || empty($l['max']) || $l['max'] <= $l['min'])
return '';
$stack = [];
for ($i = $l['min']; $i <= $l['max']; $i++)
$stack[$i] = round(100 / (1 + $l['max'] - $l['min']), 3);
// yes, it wants a string .. how weired is that..
return json_encode($stack, JSON_NUMERIC_CHECK); // do not replace with Util::toJSON !
}
private function storeJSGlobals(array $data) : void
{
foreach ($data as $type => $jsData)
{
foreach ($jsData as $k => $v)
{
// was already set at some point with full data
if (isset($this->jsGlobals[$type][$k]) && is_array($this->jsGlobals[$type][$k]))
continue;
$this->jsGlobals[$type][$k] = $v;
}
}
}
private function calcChance(array $refs, array $parents = []) : array
{
$retData = [];
$retKeys = [];
foreach ($refs as $rId => $ref)
{
// check for possible database inconsistencies
if (!$ref['chance'] && !$ref['isGrouped'])
trigger_error('Loot by Item: Ungrouped Item/Ref '.$ref['item'].' has 0% chance assigned!', E_USER_WARNING);
if ($ref['isGrouped'] && $ref['sumChance'] > 100)
trigger_error('Loot by Item: Group with Item/Ref '.$ref['item'].' has '.number_format($ref['sumChance'], 2).'% total chance! Some items cannot drop!', E_USER_WARNING);
if ($ref['isGrouped'] && $ref['sumChance'] >= 100 && !$ref['chance'])
trigger_error('Loot by Item: Item/Ref '.$ref['item'].' with adaptive chance cannot drop. Group already at 100%!', E_USER_WARNING);
$chance = abs($ref['chance'] ?: (100 - $ref['sumChance']) / $ref['nZeroItems']) / 100;
// apply inherited chanceMods
if (isset($this->chanceMods[$ref['item']]))
{
$chance *= $this->chanceMods[$ref['item']][0];
$chance = 1 - pow(1 - $chance, $this->chanceMods[$ref['item']][1]);
}
// save chance for parent-ref
$this->chanceMods[$rId] = [$chance, $ref['multiplier']];
// refTemplate doesn't point to a new ref -> we are done
if (!in_array($rId, $parents))
{
$data = array(
'percent' => $chance,
'stack' => [$ref['min'], $ref['max']],
'count' => 1 // ..and one for the sort script
);
if ($_ = self::createStack($ref))
$data['pctstack'] = $_;
// sort highest chances first
$i = 0;
for (; $i < count($retData); $i++)
if ($retData[$i]['percent'] < $data['percent'])
break;
array_splice($retData, $i, 0, [$data]);
array_splice($retKeys, $i, 0, [$rId]);
}
}
return array_combine($retKeys, $retData);
}
private function getByContainerRecursive(string $tableName, int $lootId, array &$handledRefs, int $groupId = 0, float $baseChance = 1.0) : array
{
$loot = [];
$rawItems = [];
if (!$tableName || !$lootId)
return [null, null];
$rows = DB::World()->select('SELECT * FROM ?# WHERE entry = ?d{ AND groupid = ?d}', $tableName, $lootId, $groupId ?: DBSIMPLE_SKIP);
if (!$rows)
return [null, null];
$groupChances = [];
$nGroupEquals = [];
$cnd = new Conditions();
foreach ($rows as $entry)
{
$set = array(
'quest' => $entry['QuestRequired'],
'group' => $entry['GroupId'],
'parentRef' => $tableName == LOOT_REFERENCE ? $lootId : 0,
'realChanceMod' => $baseChance,
'groupChance' => 0
);
if ($entry['QuestRequired'])
foreach (DB::Aowow()->selectCol('SELECT id FROM ?_quests WHERE (`reqSourceItemId1` = ?d OR `reqSourceItemId2` = ?d OR `reqSourceItemId3` = ?d OR `reqSourceItemId4` = ?d OR `reqItemId1` = ?d OR `reqItemId2` = ?d OR `reqItemId3` = ?d OR `reqItemId4` = ?d OR `reqItemId5` = ?d OR `reqItemId6` = ?d) AND (`cuFlags` & ?d) = 0',
$entry['Item'], $entry['Item'], $entry['Item'], $entry['Item'], $entry['Item'], $entry['Item'], $entry['Item'], $entry['Item'], $entry['Item'], $entry['Item'], CUSTOM_EXCLUDE_FOR_LISTVIEW | CUSTOM_UNAVAILABLE) as $questId)
$cnd->addExternalCondition(Conditions::lootTableToConditionSource($tableName), $lootId . ':' . $entry['Item'], [Conditions::QUESTTAKEN, $questId]);
// if ($entry['LootMode'] > 1)
// {
$buff = [];
for ($i = 0; $i < 8; $i++)
if ($entry['LootMode'] & (1 << $i))
$buff[] = $i + 1;
$set['mode'] = implode(', ', $buff);
// }
// else
// $set['mode'] = 0;
/*
modes:{"mode":8,"4":{"count":7173,"outof":17619},"8":{"count":7173,"outof":10684}}
ignore lootmodes from sharedDefines.h use different creatures/GOs from each template
modes.mode = b6543210
||||||'dungeon heroic
|||||'dungeon normal
||||'<empty>
|||'10man normal
||'25man normal
|'10man heroic
'25man heroic
*/
if ($entry['Reference'])
{
// bandaid.. remove when propperly handling lootmodes
if (!in_array($entry['Reference'], $handledRefs))
{ // todo (high): find out, why i used this in the first place. (don't do drugs, kids)
[$data, $raw] = self::getByContainerRecursive(LOOT_REFERENCE, $entry['Reference'], $handledRefs, /*$entry['GroupId'],*/ 0, $entry['Chance'] / 100);
$handledRefs[] = $entry['Reference'];
$loot = array_merge($loot, $data);
$rawItems = array_merge($rawItems, $raw);
}
$set['reference'] = $entry['Reference'];
$set['multiplier'] = $entry['MaxCount'];
}
else
{
$rawItems[] = $entry['Item'];
$set['content'] = $entry['Item'];
$set['min'] = $entry['MinCount'];
$set['max'] = $entry['MaxCount'];
}
if (!isset($groupChances[$entry['GroupId']]))
{
$groupChances[$entry['GroupId']] = 0;
$nGroupEquals[$entry['GroupId']] = 0;
}
if ($set['quest'] || !$set['group'])
$set['groupChance'] = $entry['Chance'];
else if ($entry['GroupId'] && !$entry['Chance'])
{
$nGroupEquals[$entry['GroupId']]++;
$set['groupChance'] = &$groupChances[$entry['GroupId']];
}
else if ($entry['GroupId'] && $entry['Chance'])
{
$set['groupChance'] = $entry['Chance'];
if (!$entry['Reference'])
{
if (empty($groupChances[$entry['GroupId']]))
$groupChances[$entry['GroupId']] = 0;
$groupChances[$entry['GroupId']] += $entry['Chance'];
}
}
else // shouldn't have happened
{
trigger_error('Unhandled case in calculating chance for item '.$entry['Item'].'!', E_USER_WARNING);
continue;
}
$loot[] = $set;
}
foreach (array_keys($nGroupEquals) as $k)
{
$sum = $groupChances[$k];
if (!$sum)
$sum = 0;
else if ($sum >= 100.01)
{
trigger_error('Loot entry '.$lootId.' / group '.$k.' has a total chance of '.number_format($sum, 2).'%. Some items cannot drop!', E_USER_WARNING);
$sum = 100;
}
// is applied as backReference to items with 0-chance
$groupChances[$k] = (100 - $sum) / ($nGroupEquals[$k] ?: 1);
}
if ($cnd->getBySourceGroup($lootId, Conditions::lootTableToConditionSource($tableName))->prepare())
{
self::storeJSGlobals($cnd->getJsGlobals());
$cnd->toListviewColumn($loot, $this->extraCols, $lootId, 'content');
}
return [$loot, array_unique($rawItems)];
}
public function getByContainer(string $table, int $entry): bool
{
$this->entry = intVal($entry);
if (!in_array($table, $this->lootTemplates) || !$this->entry)
return false;
/*
// if (is_array($this->entry) && in_array($table, [LOOT_CREATURE, LOOT_GAMEOBJECT])
// iterate over the 4 available difficulties and assign modes
modes:{"mode":1,"1":{"count":4408,"outof":16013},"4":{"count":4408,"outof":22531}}
*/
$handledRefs = [];
[$lootRows, $itemIds] = self::getByContainerRecursive($table, $this->entry, $handledRefs);
if (!$lootRows)
return false;
$items = new ItemList(array(['i.id', $itemIds], Cfg::get('SQL_LIMIT_NONE')));
self::storeJSGlobals($items->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED));
$foo = $items->getListviewData();
// assign listview LV rows to loot rows, not the other way round! The same item may be contained multiple times
foreach ($lootRows as $loot)
{
$base = array(
'percent' => round($loot['groupChance'] * $loot['realChanceMod'], 3),
'group' => $loot['group'],
'quest' => $loot['quest'],
'count' => 1 // satisfies the sort-script
);
if ($_ = $loot['mode'])
$base['mode'] = $_;
if ($_ = $loot['parentRef'])
$base['reference'] = $_;
if (isset($loot['condition']))
$base['condition'] = $loot['condition'];
if ($_ = self::createStack($loot))
$base['pctstack'] = $_;
if (empty($loot['reference'])) // regular drop
{
if (!isset($foo[$loot['content']]))
{
trigger_error('Item #'.$loot['content'].' referenced by loot does not exist!', E_USER_WARNING);
continue;
}
if (!User::isInGroup(U_GROUP_EMPLOYEE))
{
if (!isset($this->results[$loot['content']]))
$this->results[$loot['content']] = array_merge($foo[$loot['content']], $base, ['stack' => [$loot['min'], $loot['max']]]);
else
$this->results[$loot['content']]['percent'] += $base['percent'];
}
else // in case of limited trash loot, check if $foo[<itemId>] exists
$this->results[] = array_merge($foo[$loot['content']], $base, ['stack' => [$loot['min'], $loot['max']]]);
}
else if (User::isInGroup(U_GROUP_EMPLOYEE)) // create dummy for ref-drop
{
$data = array(
'id' => $loot['reference'],
'name' => '@REFERENCE: '.$loot['reference'],
'icon' => 'trade_engineering',
'stack' => [$loot['multiplier'], $loot['multiplier']]
);
$this->results[] = array_merge($base, $data);
$this->jsGlobals[Type::ITEM][$loot['reference']] = $data;
}
}
// move excessive % to extra loot
if (!User::isInGroup(U_GROUP_EMPLOYEE))
{
foreach ($this->results as &$_)
{
if ($_['percent'] <= 100)
continue;
while ($_['percent'] > 200)
{
$_['stack'][0]++;
$_['stack'][1]++;
$_['percent'] -= 100;
}
$_['stack'][1]++;
$_['percent'] = 100;
}
}
else
{
$fields = ['mode', 'reference'];
$base = [];
$set = 0;
foreach ($this->results as $foo)
{
foreach ($fields as $idx => $field)
{
$val = isset($foo[$field]) ? $foo[$field] : 0;
if (!isset($base[$idx]))
$base[$idx] = $val;
else if ($base[$idx] != $val)
$set |= 1 << $idx;
}
if ($set == (pow(2, count($fields)) - 1))
break;
}
$this->extraCols[] = "\$Listview.funcBox.createSimpleCol('group', 'Group', '7%', 'group')";
foreach ($fields as $idx => $field)
if ($set & (1 << $idx))
$this->extraCols[] = "\$Listview.funcBox.createSimpleCol('".$field."', '".Util::ucFirst($field)."', '7%', '".$field."')";
}
return true;
}
public function getByItem(int $entry, int $maxResults = -1, array $lootTableList = []) : bool
{
$this->entry = $entry;
if (!$this->entry)
return false;
if ($maxResults < 0)
$maxResults = Cfg::get('SQL_LIMIT_DEFAULT');
// [fileName, tabData, tabName, tabId, extraCols, hiddenCols, visibleCols]
$tabsFinal = array(
[Type::ITEM, [], '$LANG.tab_containedin', 'contained-in-item', [], [], []],
[Type::ITEM, [], '$LANG.tab_disenchantedfrom', 'disenchanted-from', [], [], []],
[Type::ITEM, [], '$LANG.tab_prospectedfrom', 'prospected-from', [], [], []],
[Type::ITEM, [], '$LANG.tab_milledfrom', 'milled-from', [], [], []],
[Type::NPC, [], '$LANG.tab_droppedby', 'dropped-by', [], [], []],
[Type::NPC, [], '$LANG.tab_pickpocketedfrom', 'pickpocketed-from', [], [], []],
[Type::NPC, [], '$LANG.tab_skinnedfrom', 'skinned-from', [], [], []],
[Type::NPC, [], '$LANG.tab_minedfromnpc', 'mined-from-npc', [], [], []],
[Type::NPC, [], '$LANG.tab_salvagedfrom', 'salvaged-from', [], [], []],
[Type::NPC, [], '$LANG.tab_gatheredfromnpc', 'gathered-from-npc', [], [], []],
[Type::QUEST, [], '$LANG.tab_rewardfrom', 'reward-from-quest', [], [], []],
[Type::ZONE, [], '$LANG.tab_fishedin', 'fished-in-zone', [], [], []],
[Type::OBJECT, [], '$LANG.tab_containedin', 'contained-in-object', [], [], []],
[Type::OBJECT, [], '$LANG.tab_minedfrom', 'mined-from-object', [], [], []],
[Type::OBJECT, [], '$LANG.tab_gatheredfrom', 'gathered-from-object', [], [], []],
[Type::OBJECT, [], '$LANG.tab_fishedin', 'fished-in-object', [], [], []],
[Type::SPELL, [], '$LANG.tab_createdby', 'created-by', [], [], []],
[Type::ACHIEVEMENT, [], '$LANG.tab_rewardfrom', 'reward-from-achievement', [], [], []]
);
$refResults = [];
$query = 'SELECT
lt1.entry AS ARRAY_KEY,
IF(lt1.reference = 0, lt1.item, lt1.reference) AS item,
lt1.chance,
SUM(IF(lt2.chance = 0, 1, 0)) AS nZeroItems,
SUM(IF(lt2.reference = 0, lt2.chance, 0)) AS sumChance,
IF(lt1.groupid > 0, 1, 0) AS isGrouped,
IF(lt1.reference = 0, lt1.mincount, 1) AS min,
IF(lt1.reference = 0, lt1.maxcount, 1) AS max,
IF(lt1.reference > 0, lt1.maxcount, 1) AS multiplier
FROM
?# lt1
LEFT JOIN
?# lt2 ON lt1.entry = lt2.entry AND lt1.groupid = lt2.groupid
WHERE
%s
GROUP BY lt2.entry, lt2.groupid';
/*
get references containing the item
*/
$newRefs = DB::World()->select(
sprintf($query, 'lt1.item = ?d AND lt1.reference = 0'),
LOOT_REFERENCE, LOOT_REFERENCE,
$this->entry
);
/* i'm currently not seeing a reasonable way to blend this into creature/gobject/etc tabs as one entity may drop the same item multiple times, with and without conditions.
if ($newRefs)
{
$cnd = new Conditions();
if ($cnd->getBySourceEntry($this->entry, Conditions::SRC_REFERENCE_LOOT_TEMPLATE))
if ($cnd->toListviewColumn($newRefs, $x, $this->entry))
self::storejsGlobals($cnd->getJsGlobals());
}
*/
while ($newRefs)
{
$curRefs = $newRefs;
$newRefs = DB::World()->select(
sprintf($query, 'lt1.reference IN (?a)'),
LOOT_REFERENCE, LOOT_REFERENCE,
array_keys($curRefs)
);
$refResults += $this->calcChance($curRefs, array_column($newRefs, 'item'));
}
/*
search the real loot-templates for the itemId and gathered refds
*/
foreach ($this->lootTemplates as $lootTemplate)
{
if ($lootTableList && !in_array($lootTemplate, $lootTableList))
continue;
if ($lootTemplate == LOOT_REFERENCE)
continue;
$result = $this->calcChance(DB::World()->select(
sprintf($query, '{lt1.reference IN (?a) OR }(lt1.reference = 0 AND lt1.item = ?d)'),
$lootTemplate, $lootTemplate,
$refResults ? array_keys($refResults) : DBSIMPLE_SKIP,
$this->entry
));
// do not skip here if $result is empty. Additional loot for spells and quest is added separately
// format for actual use
foreach ($result as $k => $v)
{
unset($result[$k]);
$v['percent'] = round($v['percent'] * 100, 3);
$result[abs($k)] = $v;
}
// cap fetched entries to the sql-limit to guarantee, that the highest chance items get selected first
// screws with GO-loot and skinning-loot as these templates are shared for several tabs (fish, herb, ore) (herb, ore, leather)
$ids = array_slice(array_keys($result), 0, $maxResults);
switch ($lootTemplate)
{
case LOOT_CREATURE: $field = 'lootId'; $tabId = 4; break;
case LOOT_PICKPOCKET: $field = 'pickpocketLootId'; $tabId = 5; break;
case LOOT_SKINNING: $field = 'skinLootId'; $tabId = -6; break; // assigned later
case LOOT_PROSPECTING: $field = 'id'; $tabId = 2; break;
case LOOT_MILLING: $field = 'id'; $tabId = 3; break;
case LOOT_ITEM: $field = 'id'; $tabId = 0; break;
case LOOT_DISENCHANT: $field = 'disenchantId'; $tabId = 1; break;
case LOOT_FISHING: $field = 'id'; $tabId = 11; break; // subAreas are currently ignored
case LOOT_GAMEOBJECT:
if (!$ids)
continue 2;
$srcObj = new GameObjectList(array(['lootId', $ids]));
if ($srcObj->error)
continue 2;
$srcData = $srcObj->getListviewData();
foreach ($srcObj->iterate() as $curTpl)
{
switch ($curTpl['typeCat'])
{
case 25: $tabId = 15; break; // fishing node
case -3: $tabId = 14; break; // herb
case -4: $tabId = 13; break; // vein
default: $tabId = 12; break; // general chest loot
}
$tabsFinal[$tabId][1][] = array_merge($srcData[$srcObj->id], $result[$srcObj->getField('lootId')]);
$tabsFinal[$tabId][4][] = '$Listview.extraCols.percent';
if ($tabId != 15)
$tabsFinal[$tabId][6][] = 'skill';
}
continue 2;
case LOOT_MAIL:
// quest part
$conditions = array(['rewardChoiceItemId1', $this->entry], ['rewardChoiceItemId2', $this->entry], ['rewardChoiceItemId3', $this->entry], ['rewardChoiceItemId4', $this->entry], ['rewardChoiceItemId5', $this->entry],
['rewardChoiceItemId6', $this->entry], ['rewardItemId1', $this->entry], ['rewardItemId2', $this->entry], ['rewardItemId3', $this->entry], ['rewardItemId4', $this->entry],
'OR');
if ($ids)
$conditions[] = ['rewardMailTemplateId', $ids];
$srcObj = new QuestList($conditions);
if (!$srcObj->error)
{
self::storeJSGlobals($srcObj->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_REWARDS));
$srcData = $srcObj->getListviewData();
foreach ($srcObj->iterate() as $_)
$tabsFinal[10][1][] = array_merge($srcData[$srcObj->id], empty($result[$srcObj->id]) ? ['percent' => -1] : $result[$srcObj->id]);
}
// achievement part
$conditions = array(['itemExtra', $this->entry]);
if ($ar = DB::World()->selectCol('SELECT ID FROM achievement_reward WHERE ItemID = ?d{ OR MailTemplateID IN (?a)}', $this->entry, $ids ?: DBSIMPLE_SKIP))
array_push($conditions, ['id', $ar], 'OR');
$srcObj = new AchievementList($conditions);
if (!$srcObj->error)
{
self::storeJSGlobals($srcObj->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_REWARDS));
$srcData = $srcObj->getListviewData();
foreach ($srcObj->iterate() as $_)
$tabsFinal[17][1][] = array_merge($srcData[$srcObj->id], empty($result[$srcObj->id]) ? ['percent' => -1] : $result[$srcObj->id]);
$tabsFinal[17][5][] = 'rewards';
$tabsFinal[17][6][] = 'category';
}
continue 2;
case LOOT_SPELL:
$conditions = array(
'OR',
['AND', ['effect1CreateItemId', $this->entry], ['OR', ['effect1Id', SpellList::EFFECTS_ITEM_CREATE], ['effect1AuraId', SpellList::AURAS_ITEM_CREATE]]],
['AND', ['effect2CreateItemId', $this->entry], ['OR', ['effect2Id', SpellList::EFFECTS_ITEM_CREATE], ['effect2AuraId', SpellList::AURAS_ITEM_CREATE]]],
['AND', ['effect3CreateItemId', $this->entry], ['OR', ['effect3Id', SpellList::EFFECTS_ITEM_CREATE], ['effect3AuraId', SpellList::AURAS_ITEM_CREATE]]],
);
if ($ids)
$conditions[] = ['id', $ids];
$srcObj = new SpellList($conditions);
if (!$srcObj->error)
{
self::storeJSGlobals($srcObj->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED));
$srcData = $srcObj->getListviewData();
if (!empty($result))
$tabsFinal[16][4][] = '$Listview.extraCols.percent';
if ($srcObj->hasSetFields('reagent1', 'reagent2', 'reagent3', 'reagent4', 'reagent5', 'reagent6', 'reagent7', 'reagent8'))
$tabsFinal[16][6][] = 'reagents';
foreach ($srcObj->iterate() as $_)
$tabsFinal[16][1][] = array_merge($srcData[$srcObj->id], empty($result[$srcObj->id]) ? ['percent' => -1] : $result[$srcObj->id]);
}
continue 2;
}
if (!$ids)
continue;
$parentData = [];
switch ($tabsFinal[abs($tabId)][0])
{
case TYPE::NPC: // new CreatureList
if ($baseIds = DB::Aowow()->selectCol(
'SELECT `difficultyEntry1` AS ARRAY_KEY, `id` FROM ?_creature WHERE difficultyEntry1 IN (?a) UNION
SELECT `difficultyEntry2` AS ARRAY_KEY, `id` FROM ?_creature WHERE difficultyEntry2 IN (?a) UNION
SELECT `difficultyEntry3` AS ARRAY_KEY, `id` FROM ?_creature WHERE difficultyEntry3 IN (?a)',
$ids, $ids, $ids))
{
$parentObj = new CreatureList(array(['id', $baseIds]));
if (!$parentObj->error)
{
self::storeJSGlobals($parentObj->getJSGlobals());
$parentData = $parentObj->getListviewData();
$ids = array_diff($ids, $baseIds);
}
}
case Type::ITEM: // new ItemList
case Type::ZONE: // new ZoneList
$srcObj = Type::newList($tabsFinal[abs($tabId)][0], array([$field, $ids]));
if (!$srcObj->error)
{
$srcData = $srcObj->getListviewData();
self::storeJSGlobals($srcObj->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED));
foreach ($srcObj->iterate() as $curTpl)
{
if ($tabId < 0 && $curTpl['typeFlags'] & NPC_TYPEFLAG_HERBLOOT)
$tabId = 9;
else if ($tabId < 0 && $curTpl['typeFlags'] & NPC_TYPEFLAG_ENGINEERLOOT)
$tabId = 8;
else if ($tabId < 0 && $curTpl['typeFlags'] & NPC_TYPEFLAG_MININGLOOT)
$tabId = 7;
else if ($tabId < 0)
$tabId = abs($tabId); // general case (skinning)
if (($p = $srcObj->getField('parentId')) && ($d = $parentData[$p] ?? null))
$tabsFinal[$tabId][1][] = array_merge($d, $result[$srcObj->getField($field)]);
else
$tabsFinal[$tabId][1][] = array_merge($srcData[$srcObj->id], $result[$srcObj->getField($field)]);
$tabsFinal[$tabId][4][] = '$Listview.extraCols.percent';
}
}
break;
}
}
foreach ($tabsFinal as $tabId => $data)
{
$tabData = array(
'data' => $data[1],
'name' => $data[2],
'id' => $data[3]
);
if ($data[4])
$tabData['extraCols'] = array_unique($data[4]);
if ($data[5])
$tabData['hiddenCols'] = array_unique($data[5]);
if ($data[6])
$tabData['visibleCols'] = array_unique($data[6]);
$this->results[$tabId] = [Type::getFileString($data[0]), $tabData];
}
return true;
}
}
?>