mirror of
https://github.com/Sarjuuk/aowow.git
synced 2025-11-29 15:58:16 +08:00
Loot/Modes
* work against more correctly assigning instance mode to entities and loot
- added manually collected data for difficulty versions of gameobjects, just boss chests for now.
update setup/source to default object source to base difficulty version if able
- update spelldifficulty table to contain the (likely) mapmode it will be used in
* refactored class loot
- implement loot mode indicators on listview for creature and gameobject loot
- show 'drops' listview tab on instance zone page
- fixes against tribute chest systems (toc / ulduar)
- fix icc gunship battle chest ownership
This commit is contained in:
@@ -117,9 +117,9 @@ class CurrencyBaseResponse extends TemplateResponse implements ICache
|
||||
if ($this->typeId != CURRENCY_HONOR_POINTS && $this->typeId != CURRENCY_ARENA_POINTS)
|
||||
{
|
||||
// tabs: this currency is contained in..
|
||||
$lootTabs = new Loot();
|
||||
$lootTabs = new LootByItem($_relItemId);
|
||||
|
||||
if ($lootTabs->getByItem($_relItemId))
|
||||
if ($lootTabs->getByItem())
|
||||
{
|
||||
$this->extendGlobalData($lootTabs->jsGlobals);
|
||||
|
||||
@@ -198,7 +198,7 @@ class CurrencyBaseResponse extends TemplateResponse implements ICache
|
||||
}
|
||||
}
|
||||
|
||||
// tab: created by (spell) [for items its handled in Loot::getByContainer()]
|
||||
// tab: created by (spell) [for items its handled in LootByItem]
|
||||
if ($this->typeId == CURRENCY_HONOR_POINTS)
|
||||
{
|
||||
$createdBy = new SpellList(array(['effect1Id', SPELL_EFFECT_ADD_HONOR], ['effect2Id', SPELL_EFFECT_ADD_HONOR], ['effect3Id', SPELL_EFFECT_ADD_HONOR], 'OR'));
|
||||
|
||||
@@ -443,9 +443,9 @@ class ItemBaseResponse extends TemplateResponse implements ICache
|
||||
}
|
||||
|
||||
// tabs: this item is contained in..
|
||||
$lootTabs = new Loot();
|
||||
$lootTabs = new LootByItem($this->typeId);
|
||||
$createdBy = [];
|
||||
if ($lootTabs->getByItem($this->typeId))
|
||||
if ($lootTabs->getByItem())
|
||||
{
|
||||
$this->extendGlobalData($lootTabs->jsGlobals);
|
||||
|
||||
@@ -454,27 +454,26 @@ class ItemBaseResponse extends TemplateResponse implements ICache
|
||||
if (!$tabData['data'])
|
||||
continue;
|
||||
|
||||
if ($idx == 16)
|
||||
if ($idx == LootByItem::SPELL_CREATED)
|
||||
$createdBy = array_column($tabData['data'], 'id');
|
||||
|
||||
if ($idx == 1)
|
||||
if ($idx == LootByItem::ITEM_DISENCHANTED)
|
||||
$tabData['note'] = sprintf(Util::$filterResultString, '?items&filter=cr=163;crs='.$this->typeId.';crv=0');
|
||||
|
||||
if ($idx == 4 && $this->subject->getSources($s, $sm) && $s[0] == SRC_DROP && isset($sm[0]['dd']))
|
||||
if ($idx == LootByItem::NPC_DROPPED && $this->subject->getSources($s, $sm) && $s[0] == SRC_DROP && isset($sm[0]['dd']))
|
||||
$tabData['note'] = match($sm[0]['dd'])
|
||||
{
|
||||
switch ($sm[0]['dd'])
|
||||
{
|
||||
case -1: $tabData['note'] = '$LANG.lvnote_itemdropsinnormalonly'; break;
|
||||
case -2: $tabData['note'] = '$LANG.lvnote_itemdropsinheroiconly'; break;
|
||||
case -3: $tabData['note'] = '$LANG.lvnote_itemdropsinnormalheroic'; break;
|
||||
case 1: $tabData['note'] = '$LANG.lvnote_itemdropsinnormal10only'; break;
|
||||
case 2: $tabData['note'] = '$LANG.lvnote_itemdropsinnormal25only'; break;
|
||||
case 3: $tabData['note'] = '$LANG.lvnote_itemdropsinheroic10only'; break;
|
||||
case 4: $tabData['note'] = '$LANG.lvnote_itemdropsinheroic25only'; break;
|
||||
}
|
||||
}
|
||||
-1 => '$LANG.lvnote_itemdropsinnormalonly',
|
||||
-2 => '$LANG.lvnote_itemdropsinheroiconly',
|
||||
-3 => '$LANG.lvnote_itemdropsinnormalheroic',
|
||||
1 => '$LANG.lvnote_itemdropsinnormal10only',
|
||||
2 => '$LANG.lvnote_itemdropsinnormal25only',
|
||||
3 => '$LANG.lvnote_itemdropsinheroic10only',
|
||||
4 => '$LANG.lvnote_itemdropsinheroic25only',
|
||||
default => null
|
||||
};
|
||||
|
||||
if ($idx == 15 && !$this->map)
|
||||
if ($idx == LootByItem::OBJECT_FISHED && !$this->map)
|
||||
{
|
||||
$nodeIds = array_map(fn($x) => $x['id'], $tabData['data']);
|
||||
$fishedIn = new GameObjectList(array(['id', $nodeIds]));
|
||||
@@ -505,16 +504,16 @@ class ItemBaseResponse extends TemplateResponse implements ICache
|
||||
|
||||
// tabs: this item contains..
|
||||
$sourceFor = array(
|
||||
[LOOT_ITEM, $this->typeId, '$LANG.tab_contains', 'contains', ['$Listview.extraCols.percent'], [] ],
|
||||
[LOOT_PROSPECTING, $this->typeId, '$LANG.tab_prospecting', 'prospecting', ['$Listview.extraCols.percent'], ['side', 'slot', 'reqlevel']],
|
||||
[LOOT_MILLING, $this->typeId, '$LANG.tab_milling', 'milling', ['$Listview.extraCols.percent'], ['side', 'slot', 'reqlevel']],
|
||||
[LOOT_DISENCHANT, $this->subject->getField('disenchantId'), '$LANG.tab_disenchanting', 'disenchanting', ['$Listview.extraCols.percent'], ['side', 'slot', 'reqlevel']]
|
||||
[Loot::ITEM, $this->typeId, '$LANG.tab_contains', 'contains', ['$Listview.extraCols.percent'], [] ],
|
||||
[Loot::PROSPECTING, $this->typeId, '$LANG.tab_prospecting', 'prospecting', ['$Listview.extraCols.percent'], ['side', 'slot', 'reqlevel']],
|
||||
[Loot::MILLING, $this->typeId, '$LANG.tab_milling', 'milling', ['$Listview.extraCols.percent'], ['side', 'slot', 'reqlevel']],
|
||||
[Loot::DISENCHANT, $this->subject->getField('disenchantId'), '$LANG.tab_disenchanting', 'disenchanting', ['$Listview.extraCols.percent'], ['side', 'slot', 'reqlevel']]
|
||||
);
|
||||
|
||||
foreach ($sourceFor as [$lootTemplate, $lootId, $tabName, $tabId, $extraCols, $hiddenCols])
|
||||
{
|
||||
$lootTab = new Loot();
|
||||
if ($lootTab->getByContainer($lootTemplate, $lootId))
|
||||
$lootTab = new LootByContainer();
|
||||
if ($lootTab->getByContainer($lootTemplate, [$lootId]))
|
||||
{
|
||||
$this->extendGlobalData($lootTab->jsGlobals);
|
||||
$extraCols = array_merge($extraCols, $lootTab->extraCols);
|
||||
@@ -523,6 +522,7 @@ class ItemBaseResponse extends TemplateResponse implements ICache
|
||||
'data' => $lootTab->getResult(),
|
||||
'name' => $tabName,
|
||||
'id' => $tabId,
|
||||
'computeDataFunc' => '$Listview.funcBox.initLootTable'
|
||||
);
|
||||
|
||||
if ($extraCols)
|
||||
|
||||
@@ -103,10 +103,9 @@ class NpcBaseResponse extends TemplateResponse implements ICache
|
||||
/**********************/
|
||||
|
||||
$mapType = 0;
|
||||
if ($maps = DB::Aowow()->selectCol('SELECT DISTINCT `areaId` FROM ?_spawns WHERE `type` = ?d AND `typeId` = ?d', Type::NPC, $this->typeId))
|
||||
if ($maps = DB::Aowow()->selectCell('SELECT IF(COUNT(DISTINCT `areaId`) > 1, 0, `areaId`) FROM ?_spawns WHERE `type` = ?d AND `typeId` = ?d', Type::NPC, $this->typeId))
|
||||
{
|
||||
if (count($maps) == 1) // should only exist in one instance
|
||||
$mapType = match (DB::Aowow()->selectCell('SELECT `type` FROM ?_zones WHERE `id` = ?d', $maps[0]))
|
||||
$mapType = match ((int)DB::Aowow()->selectCell('SELECT `type` FROM ?_zones WHERE `id` = ?d', $maps[0]))
|
||||
{
|
||||
// MAP_TYPE_DUNGEON,
|
||||
MAP_TYPE_DUNGEON_HC => 1,
|
||||
@@ -244,7 +243,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
|
||||
}
|
||||
|
||||
if ($stats = $this->getCreatureStats($mapType, $_altIds))
|
||||
$infobox[] = Lang::npc('stats').($_altIds ? ' ('.Lang::npc('modes', $mapType, 0).')' : '').Lang::main('colon').'[ul][li]'.implode('[/li][li]', $stats).'[/li][/ul]';
|
||||
$infobox[] = Lang::npc('stats').($_altIds ? ' ('.Lang::game('modes', $mapType, 0).')' : '').Lang::main('colon').'[ul][li]'.implode('[/li][li]', $stats).'[/li][/ul]';
|
||||
|
||||
if ($infobox)
|
||||
{
|
||||
@@ -566,79 +565,108 @@ class NpcBaseResponse extends TemplateResponse implements ICache
|
||||
}
|
||||
|
||||
// tabs: this creature contains..
|
||||
$skinTab = ['tab_skinning', 'skinning', SKILL_SKINNING];
|
||||
if ($_typeFlags & NPC_TYPEFLAG_SKIN_WITH_HERBALISM)
|
||||
$skinTab = ['tab_herbalism', 'herbalism', SKILL_HERBALISM];
|
||||
else if ($_typeFlags & NPC_TYPEFLAG_SKIN_WITH_MINING)
|
||||
$skinTab = ['tab_mining', 'mining', SKILL_MINING];
|
||||
else if ($_typeFlags & NPC_TYPEFLAG_SKIN_WITH_ENGINEERING)
|
||||
$skinTab = ['tab_engineering', 'engineering', SKILL_ENGINEERING];
|
||||
|
||||
/*
|
||||
extraCols: [Listview.extraCols.count, Listview.extraCols.percent, Listview.extraCols.mode],
|
||||
_totalCount: 22531,
|
||||
computeDataFunc: Listview.funcBox.initLootTable,
|
||||
onAfterCreate: Listview.funcBox.addModeIndicator,
|
||||
|
||||
modes:{"mode":1,"1":{"count":4408,"outof":16013},"4":{"count":4408,"outof":22531}}
|
||||
*/
|
||||
if ($this->subject->isGatherable())
|
||||
$skinTab = ['$LANG.tab_herbalism', 'herbalism', SKILL_HERBALISM];
|
||||
else if ($this->subject->isMineable())
|
||||
$skinTab = ['$LANG.tab_mining', 'mining', SKILL_MINING];
|
||||
else if ($this->subject->isSalvageable())
|
||||
$skinTab = ['$LANG.tab_engineering', 'engineering', SKILL_ENGINEERING];
|
||||
else
|
||||
$skinTab = ['$LANG.tab_skinning', 'skinning', SKILL_SKINNING];
|
||||
|
||||
$sourceFor = array(
|
||||
0 => [LOOT_CREATURE, $this->subject->getField('lootId'), '$LANG.tab_drops', 'drops', [ ], ''],
|
||||
8 => [LOOT_PICKPOCKET, $this->subject->getField('pickpocketLootId'), '$LANG.tab_pickpocketing', 'pickpocketing', ['side', 'slot', 'reqlevel'], ''],
|
||||
9 => [LOOT_SKINNING, $this->subject->getField('skinLootId'), '$LANG.'.$skinTab[0], $skinTab[1], ['side', 'slot', 'reqlevel'], '']
|
||||
0 => [Loot::CREATURE, [4 => $this->subject->getField('lootId')], '$LANG.tab_drops', 'drops', [ ], ''],
|
||||
1 => [Loot::GAMEOBJECT, [], '$LANG.tab_drops', 'drops-object', [ ], ''],
|
||||
2 => [Loot::PICKPOCKET, [4 => $this->subject->getField('pickpocketLootId')], '$LANG.tab_pickpocketing', 'pickpocketing', ['side', 'slot', 'reqlevel'], ''],
|
||||
3 => [Loot::SKINNING, [4 => $this->subject->getField('skinLootId')], $skinTab[0], $skinTab[1], ['side', 'slot', 'reqlevel'], '']
|
||||
);
|
||||
|
||||
// temp: manually add loot for difficulty-versions
|
||||
$langref = array(
|
||||
"-2" => '$LANG.tab_heroic',
|
||||
"-1" => '$LANG.tab_normal',
|
||||
1 => '$$WH.sprintf(LANG.tab_normalX, 10)',
|
||||
2 => '$$WH.sprintf(LANG.tab_normalX, 25)',
|
||||
3 => '$$WH.sprintf(LANG.tab_heroicX, 10)',
|
||||
4 => '$$WH.sprintf(LANG.tab_heroicX, 25)'
|
||||
);
|
||||
/* loot tabs to sub tabs
|
||||
* (1 << 0) => '$LANG.tab_heroic',
|
||||
* (1 << 1) => '$LANG.tab_normal',
|
||||
* (1 << 2) => '$LANG.tab_drops',
|
||||
* (1 << 3) => '$$WH.sprintf(LANG.tab_normalX, 10)',
|
||||
* (1 << 4) => '$$WH.sprintf(LANG.tab_normalX, 25)',
|
||||
* (1 << 5) => '$$WH.sprintf(LANG.tab_heroicX, 10)',
|
||||
* (1 << 6) => '$$WH.sprintf(LANG.tab_heroicX, 25)'
|
||||
*/
|
||||
|
||||
$getBit = function(int $type, int $difficulty) : int
|
||||
{
|
||||
if ($type == 1) // dungeon
|
||||
return 1 << (2 - $difficulty);
|
||||
if ($type == 2) // raid
|
||||
return 1 << (2 + $difficulty);
|
||||
return 4; // generic case
|
||||
};
|
||||
|
||||
foreach (DB::Aowow()->select('SELECT l.`difficulty` AS ARRAY_KEY, o.`id`, o.`lootId`, o.`name_loc0`, o.`name_loc2`, o.`name_loc3`, o.`name_loc4`, o.`name_loc6`, o.`name_loc8` FROM ?_loot_link l JOIN ?_objects o ON o.`id` = l.`objectId` WHERE l.`npcId` = ?d ORDER BY `difficulty` ASC', $this->typeId) as $difficulty => $lgo)
|
||||
{
|
||||
$sourceFor[1][1][$getBit($mapType, $difficulty)] = $lgo['lootId'];
|
||||
$sourceFor[1][5] = $sourceFor[1][5] ?: '$$WH.sprintf(LANG.lvnote_npcobjectsource, '.$lgo['id'].', "'.Util::localizedString($lgo, 'name').'")';
|
||||
}
|
||||
|
||||
if ($_altIds)
|
||||
{
|
||||
$sourceFor[0][2] = $mapType == 1 ? $langref[-1] : $langref[1];
|
||||
if ($mapType == 1) // map generic loot to dungeon NH
|
||||
{
|
||||
$sourceFor[0][1] = [2 => $sourceFor[0][1][4]];
|
||||
$sourceFor[2][1] = [2 => $sourceFor[2][1][4]];
|
||||
$sourceFor[3][1] = [2 => $sourceFor[3][1][4]];
|
||||
}
|
||||
if ($mapType == 2) // map generic loot to raid 10NH
|
||||
{
|
||||
$sourceFor[0][1] = [8 => $sourceFor[0][1][4]];
|
||||
$sourceFor[2][1] = [8 => $sourceFor[2][1][4]];
|
||||
$sourceFor[3][1] = [8 => $sourceFor[3][1][4]];
|
||||
}
|
||||
|
||||
foreach ($this->altNPCs->iterate() as $id => $__)
|
||||
{
|
||||
$mode = ($_altIds[$id] + 1) * ($mapType == 1 ? -1 : 1);
|
||||
foreach (DB::Aowow()->select('SELECT o.`id`, o.`lootId`, o.`name_loc0`, o.`name_loc2`, o.`name_loc3`, o.`name_loc4`, o.`name_loc6`, o.`name_loc8`, l.`difficulty` FROM ?_loot_link l JOIN ?_objects o ON o.`id` = l.`objectId` WHERE l.`npcId` = ?d', $id) as $l)
|
||||
$sourceFor[(($l['difficulty'] - 1) * 2) + 1] = [LOOT_GAMEOBJECT, $l['lootId'], $langref[$l['difficulty'] * ($mapType == 1 ? -1 : 1)], 'drops-object-'.$l['difficulty'], [], '$$WH.sprintf(LANG.lvnote_npcobjectsource, '.$l['id'].', "'.Util::localizedString($l, 'name').'")'];
|
||||
if ($lootId = $this->altNPCs->getField('lootId'))
|
||||
$sourceFor[($mode - 1) * 2] = [LOOT_CREATURE, $lootId, $langref[$mode], 'drops-'.abs($mode), [], ''];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (DB::Aowow()->select('SELECT l.`difficulty` AS ARRAY_KEY, o.`id`, o.`lootId`, o.`name_loc0`, o.`name_loc2`, o.`name_loc3`, o.`name_loc4`, o.`name_loc6`, o.`name_loc8` FROM ?_loot_link l JOIN ?_objects o ON o.`id` = l.`objectId` WHERE l.`npcId` = ?d', $this->typeId) as $difficulty => $lgo)
|
||||
$sourceFor[(($difficulty - 1) * 2) + 1] = [LOOT_GAMEOBJECT, $lgo['lootId'], $mapType ? $langref[$difficulty * ($mapType == 1 ? -1 : 1)] : '$LANG.tab_drops', 'drops-object-'.$difficulty, [], '$$WH.sprintf(LANG.lvnote_npcobjectsource, '.$lgo['id'].', "'.Util::localizedString($lgo, 'name').'")'];
|
||||
|
||||
ksort($sourceFor);
|
||||
|
||||
foreach ($sourceFor as [$lootTpl, $lootId, $tabName, $tabId, $hiddenCols, $note])
|
||||
foreach (DB::Aowow()->select('SELECT l.`difficulty` AS ARRAY_KEY, o.`id`, o.`lootId`, o.`name_loc0`, o.`name_loc2`, o.`name_loc3`, o.`name_loc4`, o.`name_loc6`, o.`name_loc8` FROM ?_loot_link l JOIN ?_objects o ON o.`id` = l.`objectId` WHERE l.`npcId` = ?d ORDER BY `difficulty` ASC', $id) as $difficulty => $lgo)
|
||||
{
|
||||
$creatureLoot = new Loot();
|
||||
if ($creatureLoot->getByContainer($lootTpl, $lootId))
|
||||
$sourceFor[1][1][$getBit($mapType, $difficulty)] = $lgo['lootId'];
|
||||
$sourceFor[1][5] = $sourceFor[1][5] ?: '$$WH.sprintf(LANG.lvnote_npcobjectsource, '.$lgo['id'].', "'.Util::localizedString($lgo, 'name').'")';
|
||||
}
|
||||
|
||||
if ($lootId = $this->altNPCs->getField('lootId'))
|
||||
$sourceFor[0][1][$getBit($mapType, $_altIds[$id] + 1)] = $lootId;
|
||||
if ($lootId = $this->altNPCs->getField('pickpocketLootId'))
|
||||
$sourceFor[2][1][$getBit($mapType, $_altIds[$id] + 1)] = $lootId;
|
||||
if ($lootId = $this->altNPCs->getField('skinLootId'))
|
||||
$sourceFor[3][1][$getBit($mapType, $_altIds[$id] + 1)] = $lootId;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($sourceFor as [$lootTpl, $lootEntries, $tabName, $tabId, $hiddenCols, $note])
|
||||
{
|
||||
$creatureLoot = new LootByContainer();
|
||||
if ($creatureLoot->getByContainer($lootTpl, $lootEntries))
|
||||
{
|
||||
$extraCols = $creatureLoot->extraCols;
|
||||
$extraCols[] = '$Listview.extraCols.percent';
|
||||
array_push($extraCols, '$Listview.extraCols.count', '$Listview.extraCols.percent');
|
||||
if (count($lootEntries) > 1)
|
||||
$extraCols[] = '$Listview.extraCols.mode';
|
||||
|
||||
$hiddenCols[] = 'count';
|
||||
|
||||
$this->extendGlobalData($creatureLoot->jsGlobals);
|
||||
|
||||
$tabData = array(
|
||||
'data' => $creatureLoot->getResult(),
|
||||
'name' => $tabName,
|
||||
'id' => $tabId,
|
||||
'name' => $tabName,
|
||||
'extraCols' => array_unique($extraCols),
|
||||
'hiddenCols' => $hiddenCols ?: null,
|
||||
'sort' => ['-percent', 'name']
|
||||
'sort' => ['-percent', 'name'],
|
||||
'_totalCount' => 10000,
|
||||
'computeDataFunc' => '$Listview.funcBox.initLootTable',
|
||||
'onAfterCreate' => '$Listview.funcBox.addModeIndicator',
|
||||
);
|
||||
|
||||
if ($note)
|
||||
$tabData['note'] = $note;
|
||||
else if ($lootTpl == LOOT_SKINNING)
|
||||
else if ($lootTpl == Loot::SKINNING)
|
||||
$tabData['note'] = '<b>'.Lang::formatSkillBreakpoints(Game::getBreakpointsForSkill($skinTab[2], $this->subject->getField('maxLevel') * 5), Lang::FMT_HTML).'</b>';
|
||||
|
||||
$this->lvTabs->addListviewTab(new Listview($tabData, ItemList::$brickFile));
|
||||
@@ -848,7 +876,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
|
||||
|
||||
// base NPC
|
||||
if ($base = $this->getRepForId([$this->typeId], $spilledParents))
|
||||
$reputation[] = [Lang::npc('modes', 1, 0), $base];
|
||||
$reputation[] = [Lang::game('modes', 1, 0), $base];
|
||||
|
||||
// difficulty dummys
|
||||
if ($dummyIds && ($mapType == 1 || $mapType == 2))
|
||||
@@ -862,7 +890,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
|
||||
|
||||
// apply by difficulty
|
||||
foreach ($alt as $mode => $dat)
|
||||
$reputation[] = [Lang::npc('modes', $mapType, $mode), $dat];
|
||||
$reputation[] = [Lang::game('modes', $mapType, $mode), $dat];
|
||||
}
|
||||
|
||||
// get spillover factions and apply
|
||||
@@ -964,7 +992,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
|
||||
if (!$this->altNPCs->getEntry($id))
|
||||
continue;
|
||||
|
||||
$m = Lang::npc('modes', $mapType, $mode);
|
||||
$m = Lang::game('modes', $mapType, $mode);
|
||||
|
||||
// Health
|
||||
$health = $this->altNPCs->getBaseStats('health');
|
||||
|
||||
@@ -22,6 +22,9 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
|
||||
public ?Book $book = null;
|
||||
public ?array $relBoss = null;
|
||||
|
||||
private array $difficulties = [];
|
||||
private int $mapType = 0;
|
||||
|
||||
private GameObjectList $subject;
|
||||
|
||||
public function __construct(string $id)
|
||||
@@ -61,6 +64,37 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
|
||||
array_unshift($this->title, Lang::unescapeUISequences($this->subject->getField('name', true), Lang::FMT_RAW), Util::ucFirst(Lang::game('object')));
|
||||
|
||||
|
||||
/**********************/
|
||||
/* Determine Map Type */
|
||||
/**********************/
|
||||
|
||||
if ($objectdifficulty = DB::Aowow()->select( // has difficulty versions of itself
|
||||
'SELECT `normal10` AS "0", `normal25` AS "1",
|
||||
`heroic10` AS "2", `heroic25` AS "3",
|
||||
`mapType` AS ARRAY_KEY
|
||||
FROM ?_objectdifficulty
|
||||
WHERE `normal10` = ?d OR `normal25` = ?d OR
|
||||
`heroic10` = ?d OR `heroic25` = ?d',
|
||||
$this->typeId, $this->typeId, $this->typeId, $this->typeId
|
||||
))
|
||||
{
|
||||
$this->mapType = key($objectdifficulty);
|
||||
$this->difficulties = array_pop($objectdifficulty);
|
||||
}
|
||||
else if ($maps = DB::Aowow()->selectCell('SELECT IF(COUNT(DISTINCT `areaId`) > 1, 0, `areaId`) FROM ?_spawns WHERE `type` = ?d AND `typeId` = ?d', Type::OBJECT, $this->typeId))
|
||||
{
|
||||
$this->mapType = match ((int)DB::Aowow()->selectCell('SELECT `type` FROM ?_zones WHERE `id` = ?d', $maps))
|
||||
{
|
||||
// MAP_TYPE_DUNGEON,
|
||||
MAP_TYPE_DUNGEON_HC => 1,
|
||||
// MAP_TYPE_RAID,
|
||||
MAP_TYPE_MMODE_RAID,
|
||||
MAP_TYPE_MMODE_RAID_HC => 2,
|
||||
default => 0
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/***********/
|
||||
/* Infobox */
|
||||
/***********/
|
||||
@@ -198,6 +232,11 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
|
||||
if (Lang::getLocale() != Locale::EN)
|
||||
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
|
||||
|
||||
// used in mode
|
||||
foreach ($this->difficulties as $n => $id)
|
||||
if ($id == $this->typeId)
|
||||
$infobox[] = Lang::game('mode').Lang::game('modes', $this->mapType, $n);
|
||||
|
||||
// AI
|
||||
if (User::isInGroup(U_GROUP_EMPLOYEE))
|
||||
if ($_ = $this->subject->getField('ScriptOrAI'))
|
||||
@@ -402,12 +441,37 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
|
||||
// tab: contains
|
||||
if ($_ = $this->subject->getField('lootId'))
|
||||
{
|
||||
$goLoot = new Loot();
|
||||
if ($goLoot->getByContainer(LOOT_GAMEOBJECT, $_))
|
||||
// check if loot_link entry exists (only difficulty: 1)
|
||||
if ($npcId = DB::Aowow()->selectCell('SELECT `npcId` FROM ?_loot_link WHERE `objectId` = ?d AND `difficulty` = 1', $this->typeId))
|
||||
{
|
||||
// get id set of npc
|
||||
$lootEntries = DB::Aowow()->selectCol(
|
||||
'SELECT ll.`difficulty` AS ARRAY_KEY, o.`lootId`
|
||||
FROM ?_creature c
|
||||
LEFT JOIN ?_loot_link ll ON ll.`npcId` IN (c.`id`, c.`difficultyEntry1`, c.`difficultyEntry2`, c.`difficultyEntry3`)
|
||||
LEFT JOIN ?_objects o ON o.`id` = ll.`objectId`
|
||||
WHERE c.`id` = ?d
|
||||
ORDER BY ll.`difficulty` ASC',
|
||||
$npcId
|
||||
);
|
||||
|
||||
if ($this->mapType == 2 || count($lootEntries) > 2) // always raid
|
||||
$lootEntries = array_combine(array_map(fn($x) => 1 << (2 + $x), array_keys($lootEntries)), array_values($lootEntries));
|
||||
else if ($this->mapType == 1 || count($lootEntries) == 2) // dungeon or raid, assume dungeon
|
||||
$lootEntries = array_combine(array_map(fn($x) => 1 << (2 - $x), array_keys($lootEntries)), array_values($lootEntries));
|
||||
}
|
||||
else
|
||||
$lootEntries = [4 => $_];
|
||||
|
||||
$goLoot = new LootByContainer();
|
||||
if ($goLoot->getByContainer(Loot::GAMEOBJECT, $lootEntries))
|
||||
{
|
||||
$extraCols = $goLoot->extraCols;
|
||||
$extraCols[] = '$Listview.extraCols.percent';
|
||||
$hiddenCols = ['source', 'side', 'slot', 'reqlevel'];
|
||||
array_push($extraCols, '$Listview.extraCols.count', '$Listview.extraCols.percent');
|
||||
if (count($lootEntries) > 1)
|
||||
$extraCols[] = '$Listview.extraCols.mode';
|
||||
|
||||
$hiddenCols = ['source', 'side', 'slot', 'reqlevel', 'count'];
|
||||
|
||||
$this->extendGlobalData($goLoot->jsGlobals);
|
||||
$lootResult = $goLoot->getResult();
|
||||
@@ -426,7 +490,11 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
|
||||
'name' => '$LANG.tab_contains',
|
||||
'sort' => ['-percent', 'name'],
|
||||
'extraCols' => array_unique($extraCols),
|
||||
'hiddenCols' => $hiddenCols ?: null
|
||||
'hiddenCols' => $hiddenCols ?: null,
|
||||
'sort' => ['-percent', 'name'],
|
||||
'_totalCount' => 10000,
|
||||
'computeDataFunc' => '$Listview.funcBox.initLootTable',
|
||||
'onAfterCreate' => '$Listview.funcBox.addModeIndicator',
|
||||
), ItemList::$brickFile));
|
||||
}
|
||||
}
|
||||
@@ -471,6 +539,51 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
|
||||
), GameObjectList::$brickFile));
|
||||
}
|
||||
|
||||
// tab: see also
|
||||
if ($this->difficulties)
|
||||
{
|
||||
$conditions = array(
|
||||
'AND',
|
||||
['id', $this->difficulties],
|
||||
['id', $this->typeId, '!']
|
||||
);
|
||||
|
||||
$saObjects = new GameObjectList($conditions);
|
||||
if (!$saObjects->error)
|
||||
{
|
||||
$data = $saObjects->getListviewData();
|
||||
if ($this->difficulties)
|
||||
{
|
||||
$saE = ['$Listview.extraCols.mode'];
|
||||
|
||||
foreach ($data as $id => &$d)
|
||||
{
|
||||
if (($modeBit = array_search($id, $this->difficulties)) !== false)
|
||||
{
|
||||
if ($this->mapType)
|
||||
$d['modes'] = ['mode' => 1 << ($modeBit + 3)];
|
||||
else
|
||||
$d['modes'] = ['mode' => 2 - $modeBit];
|
||||
}
|
||||
else
|
||||
$d['modes'] = ['mode' => 0];
|
||||
}
|
||||
}
|
||||
|
||||
$tabData = array(
|
||||
'data' => $data,
|
||||
'id' => 'see-also',
|
||||
'name' => '$LANG.tab_seealso',
|
||||
'visibleCols' => ['level'],
|
||||
);
|
||||
|
||||
if (isset($saE))
|
||||
$tabData['extraCols'] = $saE;
|
||||
|
||||
$this->lvTabs->addListviewTab(new Listview($tabData, GameObjectList::$brickFile));
|
||||
}
|
||||
}
|
||||
|
||||
// tab: Same model as
|
||||
$sameModel = new GameObjectList(array(['displayId', $this->subject->getField('displayId')], ['id', $this->typeId, '!']));
|
||||
if (!$sameModel->error)
|
||||
|
||||
@@ -518,8 +518,8 @@ class QuestBaseResponse extends TemplateResponse implements ICache
|
||||
// todo (med): this double list creation very much sucks ...
|
||||
$getItemSource = function ($itemId, $method = 0) use (&$mapNPCs, &$mapGOs)
|
||||
{
|
||||
$lootTabs = new Loot();
|
||||
if ($lootTabs->getByItem($itemId))
|
||||
$lootTabs = new LootByItem($itemId);
|
||||
if ($lootTabs->getByItem())
|
||||
{
|
||||
/*
|
||||
todo (med): sanity check:
|
||||
@@ -1175,8 +1175,8 @@ class QuestBaseResponse extends TemplateResponse implements ICache
|
||||
$this->mail['header'][1] = Lang::mail('mailBy', [$senderTypeId, $ti]);
|
||||
|
||||
// while mail attachemnts are handled as loot, it has no variance. Always 100% chance, always one item.
|
||||
$mailLoot = new Loot();
|
||||
if ($mailLoot->getByContainer(LOOT_MAIL, $rmtId))
|
||||
$mailLoot = new LootByContainer();
|
||||
if ($mailLoot->getByContainer(Loot::MAIL, [$rmtId]))
|
||||
{
|
||||
$this->extendGlobalData($mailLoot->jsGlobals);
|
||||
foreach ($mailLoot->getResult() as $loot)
|
||||
|
||||
@@ -49,6 +49,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
|
||||
private int $firstRank = 0;
|
||||
private array $modelInfo = [];
|
||||
private array $difficulties = [];
|
||||
private int $mapType = 0;
|
||||
|
||||
public function __construct(string $id)
|
||||
{
|
||||
@@ -68,14 +69,19 @@ class SpellBaseResponse extends TemplateResponse implements ICache
|
||||
$this->extendGlobalData($jsg, $extra);
|
||||
|
||||
$this->modelInfo = $this->subject->getModelInfo($this->typeId);
|
||||
$this->difficulties = DB::Aowow()->selectRow( // has difficulty versions of itself
|
||||
if ($spelldifficulty = DB::Aowow()->select( // has difficulty versions of itself
|
||||
'SELECT `normal10` AS "0", `normal25` AS "1",
|
||||
`heroic10` AS "2", `heroic25` AS "3"
|
||||
`heroic10` AS "2", `heroic25` AS "3",
|
||||
`mapType` AS ARRAY_KEY
|
||||
FROM ?_spelldifficulty
|
||||
WHERE `normal10` = ?d OR `normal25` = ?d OR
|
||||
`heroic10` = ?d OR `heroic25` = ?d',
|
||||
$this->typeId, $this->typeId, $this->typeId, $this->typeId
|
||||
);
|
||||
))
|
||||
{
|
||||
$this->mapType = key($spelldifficulty);
|
||||
$this->difficulties = array_pop($spelldifficulty);
|
||||
}
|
||||
|
||||
// returns self or firstRank
|
||||
if ($fr = DB::World()->selectCell('SELECT `first_spell_id` FROM spell_ranks WHERE `spell_id` = ?d', $this->typeId))
|
||||
@@ -445,39 +451,28 @@ class SpellBaseResponse extends TemplateResponse implements ICache
|
||||
['s.name_loc'.Lang::getLocale()->value, $this->subject->getField('name', true)]
|
||||
);
|
||||
|
||||
if ($this->difficulties)
|
||||
$conditions = ['OR', ['AND', ...$conditions], ['AND', ['s.id', $this->difficulties], ['s.id', $this->typeId, '!']]];
|
||||
|
||||
$saSpells = new SpellList($conditions);
|
||||
if (!$saSpells->error)
|
||||
{
|
||||
$data = $saSpells->getListviewData();
|
||||
if ($this->difficulties) // needs a way to distinguish between dungeon and raid :x; creature using this -> map -> areaType?
|
||||
if ($this->difficulties)
|
||||
{
|
||||
$saE = ['$Listview.extraCols.mode'];
|
||||
|
||||
foreach ($data as $id => &$d)
|
||||
{
|
||||
if (($modeBit = array_search($id, $this->difficulties)) !== false)
|
||||
{
|
||||
if ($this->mapType)
|
||||
$d['modes'] = ['mode' => 1 << ($modeBit + 3)];
|
||||
else
|
||||
$d['modes'] = ['mode' => 2 - $modeBit];
|
||||
}
|
||||
else
|
||||
$d['modes'] = ['mode' => 0];
|
||||
|
||||
if ($this->difficulties[0] == $id) // b0001000
|
||||
{
|
||||
if (!$this->difficulties[2] && !$this->difficulties[3])
|
||||
$d['modes']['mode'] |= 0x2;
|
||||
else
|
||||
$d['modes']['mode'] |= 0x8;
|
||||
}
|
||||
|
||||
if ($this->difficulties[1] == $id) // b0010000
|
||||
{
|
||||
if (!$this->difficulties[2] && !$this->difficulties[3])
|
||||
$d['modes']['mode'] |= 0x1;
|
||||
else
|
||||
$d['modes']['mode'] |= 0x10;
|
||||
}
|
||||
|
||||
if ($this->difficulties[2] == $id) // b0100000
|
||||
$d['modes']['mode'] |= 0x20;
|
||||
|
||||
if ($this->difficulties[3] == $id) // b1000000
|
||||
$d['modes']['mode'] |= 0x40;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,8 +644,8 @@ class SpellBaseResponse extends TemplateResponse implements ICache
|
||||
|
||||
// tab: contains
|
||||
// spell_loot_template
|
||||
$spellLoot = new Loot();
|
||||
if ($spellLoot->getByContainer(LOOT_SPELL, $this->typeId))
|
||||
$spellLoot = new LootByContainer();
|
||||
if ($spellLoot->getByContainer(Loot::SPELL, [$this->typeId]))
|
||||
{
|
||||
$this->extendGlobalData($spellLoot->jsGlobals);
|
||||
|
||||
@@ -662,7 +657,8 @@ class SpellBaseResponse extends TemplateResponse implements ICache
|
||||
'name' => '$LANG.tab_contains',
|
||||
'id' => 'contains',
|
||||
'hiddenCols' => ['side', 'slot', 'source', 'reqlevel'],
|
||||
'extraCols' => array_unique($extraCols)
|
||||
'extraCols' => array_unique($extraCols),
|
||||
'computeDataFunc' => '$Listview.funcBox.initLootTable'
|
||||
), ItemList::$brickFile));
|
||||
}
|
||||
|
||||
@@ -991,7 +987,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
|
||||
}
|
||||
|
||||
// tab: taught by npc
|
||||
if ($this->subject->getSources($s) && in_array(SRC_TRAINER, $s))
|
||||
if ($this->subject->getRawSource(SRC_TRAINER))
|
||||
{
|
||||
$trainers = DB::World()->select(
|
||||
'SELECT cdt.`CreatureId` AS ARRAY_KEY, ts.`ReqSkillLine` AS "reqSkillId", ts.`ReqSkillRank` AS "reqSkillValue", ts.`ReqLevel` AS "reqLevel", ts.`ReqAbility1` AS "reqSpellId1", ts.`reqAbility2` AS "reqSpellId2"
|
||||
@@ -2423,13 +2419,10 @@ class SpellBaseResponse extends TemplateResponse implements ICache
|
||||
}
|
||||
|
||||
// accquisition.. 10: starter spell; 7: discovery
|
||||
if ($this->subject->getSources($s))
|
||||
{
|
||||
if (in_array(SRC_STARTER, $s))
|
||||
if ($this->subject->getRawSource(SRC_STARTER))
|
||||
$infobox[] = Lang::spell('starter');
|
||||
else if (in_array(SRC_DISCOVERY, $s))
|
||||
else if ($this->subject->getRawSource(SRC_DISCOVERY))
|
||||
$infobox[] = Lang::spell('discovered');
|
||||
}
|
||||
|
||||
// training cost
|
||||
if ($cost = $this->subject->getField('trainingCost'))
|
||||
@@ -2462,7 +2455,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
|
||||
// used in mode
|
||||
foreach ($this->difficulties as $n => $id)
|
||||
if ($id == $this->typeId)
|
||||
$infobox[] = Lang::game('mode').Lang::game('modes', $n);
|
||||
$infobox[] = Lang::game('mode').Lang::game('modes', $this->mapType, $n);
|
||||
|
||||
// Creature Type from Aura: Shapeshift
|
||||
foreach ($this->modelInfo as $mI)
|
||||
|
||||
@@ -565,6 +565,48 @@ class ZoneBaseResponse extends TemplateResponse implements ICache
|
||||
|
||||
$this->lvTabs = new Tabs(['parent' => "\$\$WH.ge('tabs-generic')"], 'tabsRelated', true);
|
||||
|
||||
// tab: Drops
|
||||
if (in_array($this->subject->getField('category'), [MAP_TYPE_DUNGEON, MAP_TYPE_RAID]))
|
||||
{
|
||||
// Issue 1 - if the bosses drop items that are also sold by vendors moreZoneId will be 0 as vendor location and boss location are likely in conflict with each other
|
||||
// Issue 2 - if the boss/chest isn't spawned the loot will not show up
|
||||
$items = new ItemList(array(Cfg::get('SQL_LIMIT_NONE'), ['src.moreZoneId', $this->typeId], ['src.src2', 0, '>'], ['quality', ITEM_QUALITY_UNCOMMON, '>=']), ['calcTotal' => true]);
|
||||
$data = $items->getListviewData();
|
||||
$subTabs = false;
|
||||
foreach ($items->iterate() as $id => $__)
|
||||
{
|
||||
$src = $items->getRawSource(SRC_DROP);
|
||||
$map = ($items->getField('moreMask') ?: 0) & (SRC_FLAG_DUNGEON_DROP | SRC_FLAG_RAID_DROP);
|
||||
if (!$src || !$map)
|
||||
continue;
|
||||
|
||||
$subTabs = true;
|
||||
|
||||
if ($map & SRC_FLAG_RAID_DROP)
|
||||
$mode = ($src[0] << 3);
|
||||
else
|
||||
$mode = ($src[0] & 0x1 ? 0x2 : 0) | ($src[0] & 0x2 ? 0x1 : 0);
|
||||
|
||||
$data[$id] += ['modes' => ['mode' => $mode]];
|
||||
}
|
||||
|
||||
$tabData = array(
|
||||
'data' => $data,
|
||||
'id' => 'drops',
|
||||
'name' => '$LANG.tab_drops',
|
||||
'extraCols' => $subTabs ? ['$Listview.extraCols.mode'] : null,
|
||||
'computeDataFunc' => '$Listview.funcBox.initLootTable',
|
||||
'onAfterCreate' => $subTabs ? '$Listview.funcBox.addModeIndicator' : null
|
||||
);
|
||||
|
||||
if (!is_null(ItemListFilter::getCriteriaIndex(16, $this->typeId)))
|
||||
$tabData['note'] = sprintf(Util::$filterResultString, '?items&filter=cr=16;crs='.$this->typeId.';crv=0');
|
||||
|
||||
$this->extendGlobalData($items->getJSGlobals(GLOBALINFO_SELF));
|
||||
|
||||
$this->lvTabs->addListviewTab(new Listview($tabData, ItemList::$brickFile));
|
||||
}
|
||||
|
||||
// tab: NPCs
|
||||
if ($cSpawns && !$creatureSpawns->error)
|
||||
{
|
||||
@@ -683,8 +725,8 @@ class ZoneBaseResponse extends TemplateResponse implements ICache
|
||||
// tab: achievements
|
||||
|
||||
// tab: fished in zone
|
||||
$fish = new Loot();
|
||||
if ($fish->getByContainer(LOOT_FISHING, $this->typeId))
|
||||
$fish = new LootByContainer();
|
||||
if ($fish->getByContainer(Loot::FISHING, [$this->typeId]))
|
||||
{
|
||||
$this->extendGlobalData($fish->jsGlobals);
|
||||
$xCols = array_merge(['$Listview.extraCols.percent'], $fish->extraCols);
|
||||
@@ -701,7 +743,8 @@ class ZoneBaseResponse extends TemplateResponse implements ICache
|
||||
'id' => 'fishing',
|
||||
'extraCols' => array_unique($xCols),
|
||||
'hiddenCols' => ['side'],
|
||||
'note' => $note
|
||||
'note' => $note,
|
||||
'computeDataFunc' => '$Listview.funcBox.initLootTable'
|
||||
), ItemList::$brickFile));
|
||||
}
|
||||
|
||||
|
||||
@@ -398,22 +398,22 @@ class Conditions
|
||||
|
||||
public static function lootTableToConditionSource(string $lootTable) : int
|
||||
{
|
||||
switch ($lootTable)
|
||||
return match ($lootTable)
|
||||
{
|
||||
case LOOT_FISHING: return self::SRC_FISHING_LOOT_TEMPLATE;
|
||||
case LOOT_CREATURE: return self::SRC_CREATURE_LOOT_TEMPLATE;
|
||||
case LOOT_GAMEOBJECT: return self::SRC_GAMEOBJECT_LOOT_TEMPLATE;
|
||||
case LOOT_ITEM: return self::SRC_ITEM_LOOT_TEMPLATE;
|
||||
case LOOT_DISENCHANT: return self::SRC_DISENCHANT_LOOT_TEMPLATE;
|
||||
case LOOT_PROSPECTING: return self::SRC_PROSPECTING_LOOT_TEMPLATE;
|
||||
case LOOT_MILLING: return self::SRC_MILLING_LOOT_TEMPLATE;
|
||||
case LOOT_PICKPOCKET: return self::SRC_PICKPOCKETING_LOOT_TEMPLATE;
|
||||
case LOOT_SKINNING: return self::SRC_SKINNING_LOOT_TEMPLATE;
|
||||
case LOOT_MAIL: return self::SRC_MAIL_LOOT_TEMPLATE;
|
||||
case LOOT_SPELL: return self::SRC_SPELL_LOOT_TEMPLATE;
|
||||
case LOOT_REFERENCE: return self::SRC_REFERENCE_LOOT_TEMPLATE;
|
||||
default: return self::SRC_NONE;
|
||||
}
|
||||
Loot::FISHING => self::SRC_FISHING_LOOT_TEMPLATE,
|
||||
Loot::CREATURE => self::SRC_CREATURE_LOOT_TEMPLATE,
|
||||
Loot::GAMEOBJECT => self::SRC_GAMEOBJECT_LOOT_TEMPLATE,
|
||||
Loot::ITEM => self::SRC_ITEM_LOOT_TEMPLATE,
|
||||
Loot::DISENCHANT => self::SRC_DISENCHANT_LOOT_TEMPLATE,
|
||||
Loot::PROSPECTING => self::SRC_PROSPECTING_LOOT_TEMPLATE,
|
||||
Loot::MILLING => self::SRC_MILLING_LOOT_TEMPLATE,
|
||||
Loot::PICKPOCKET => self::SRC_PICKPOCKETING_LOOT_TEMPLATE,
|
||||
Loot::SKINNING => self::SRC_SKINNING_LOOT_TEMPLATE,
|
||||
Loot::MAIL => self::SRC_MAIL_LOOT_TEMPLATE,
|
||||
Loot::SPELL => self::SRC_SPELL_LOOT_TEMPLATE,
|
||||
Loot::REFERENCE => self::SRC_REFERENCE_LOOT_TEMPLATE,
|
||||
default => self::SRC_NONE
|
||||
};
|
||||
}
|
||||
|
||||
public static function extendListviewRow(array &$lvRow, int $srcType, int $groupKey, array $condition) : bool
|
||||
|
||||
@@ -306,6 +306,11 @@ abstract class DBTypeList
|
||||
$this->error = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* iterate over fetched templates
|
||||
*
|
||||
* @return array the current template
|
||||
*/
|
||||
public function &iterate() : \Generator
|
||||
{
|
||||
if (!$this->templates)
|
||||
@@ -483,7 +488,7 @@ abstract class DBTypeList
|
||||
't': type [always set]
|
||||
'ti': typeId [always set]
|
||||
'bd': BossDrop [0; 1] [Creature / GO]
|
||||
'dd': DungeonDifficulty [-2: DungeonHC; -1: DungeonNM; 1: Raid10NM; 2:Raid25NM; 3:Raid10HM; 4: Raid25HM] [Creature / GO]
|
||||
'dd': DungeonDifficulty [-2: DungeonHC; -1: DungeonNM; 1: Raid10NM; 2:Raid25NM; 3:Raid10HM; 4: Raid25HM; 99: filler trash] [Creature / GO]
|
||||
'q': cssQuality [Items]
|
||||
'z': zone [set when all happens in here]
|
||||
'p': PvP [pvpSourceId]
|
||||
@@ -632,7 +637,18 @@ trait spawnHelper
|
||||
$wpSum = [];
|
||||
$wpIdx = 0;
|
||||
$worldPos = [];
|
||||
$spawns = DB::Aowow()->select("SELECT * FROM ?_spawns WHERE `type` = ?d AND `typeId` IN (?a) AND `posX` > 0 AND `posY` > 0", self::$type, $this->getFoundIDs()) ?: [];
|
||||
$spawns = DB::Aowow()->select(
|
||||
'SELECT CASE WHEN z.`type` = ?d THEN 1
|
||||
WHEN z.`type` = ?d THEN 2
|
||||
WHEN z.`type` = ?d THEN 2
|
||||
ELSE 0
|
||||
END AS "mapType", s.*
|
||||
FROM ?_spawns s
|
||||
JOIN ?_zones z ON s.areaId = z.id
|
||||
WHERE s.`type` = ?d AND s.`typeId` IN (?a) AND s.`posX` > 0 AND s.`posY` > 0',
|
||||
MAP_TYPE_DUNGEON_HC, MAP_TYPE_MMODE_RAID, MAP_TYPE_MMODE_RAID_HC,
|
||||
self::$type, $this->getFoundIDs()
|
||||
) ?: [];
|
||||
|
||||
if (!$skipAdmin && User::isInGroup(U_GROUP_MODERATOR))
|
||||
if ($guids = array_column(array_filter($spawns, fn($x) => $x['guid'] > 0 || $x['type'] != Type::NPC), 'guid'))
|
||||
@@ -696,13 +712,13 @@ trait spawnHelper
|
||||
$info[2] = Lang::game('phases').Lang::main('colon').Util::asHex($s['phaseMask']);
|
||||
|
||||
if ($s['spawnMask'] == 15)
|
||||
$info[3] = Lang::game('mode').Lang::game('modes', -1);
|
||||
$info[3] = Lang::game('mode').Lang::game('modes', 0, -1);
|
||||
else if ($s['spawnMask'])
|
||||
{
|
||||
$_ = [];
|
||||
for ($i = 0; $i < 4; $i++)
|
||||
if ($s['spawnMask'] & 1 << $i)
|
||||
$_[] = Lang::game('modes', $i);
|
||||
$_[] = Lang::game('modes', $s['mapType'], $i);
|
||||
|
||||
$info[4] = Lang::game('mode').implode(', ', $_);
|
||||
}
|
||||
@@ -880,8 +896,13 @@ trait profilerHelper
|
||||
|
||||
trait sourceHelper
|
||||
{
|
||||
protected $sources = [];
|
||||
protected $sourceMore = null;
|
||||
protected array $sources = [];
|
||||
protected ?array $sourceMore = null;
|
||||
|
||||
public function getRawSource(int $src) : array
|
||||
{
|
||||
return $this->sources[$this->id][$src] ?? [];
|
||||
}
|
||||
|
||||
public function getSources(?array &$s = [], ?array &$sm = []) : bool
|
||||
{
|
||||
@@ -923,7 +944,9 @@ trait sourceHelper
|
||||
10H 0b0100 2 0b011
|
||||
25H 0b1000 3 0b100
|
||||
*/
|
||||
if ($this->curTpl['moreMask'] & SRC_FLAG_DUNGEON_DROP)
|
||||
if ($this->curTpl['moreMask'] & SRC_FLAG_COMMON)
|
||||
$sm['dd'] = 99;
|
||||
else if ($this->curTpl['moreMask'] & SRC_FLAG_DUNGEON_DROP)
|
||||
$sm['dd'] = $this->sources[$this->id][SRC_DROP][0] * -1;
|
||||
else if ($this->curTpl['moreMask'] & SRC_FLAG_RAID_DROP)
|
||||
{
|
||||
|
||||
@@ -57,8 +57,8 @@ class AchievementList extends DBTypeList
|
||||
if ($rewards[$_id]['MailTemplateID'])
|
||||
{
|
||||
// using class Loot creates an inifinite loop cirling between Loot, ItemList and SpellList or something
|
||||
// $mailSrc = new Loot();
|
||||
// $mailSrc->getByContainer(LOOT_MAIL, $rewards[$_id]['MailTemplateID']);
|
||||
// $mailSrc = new LootByContainer();
|
||||
// $mailSrc->getByContainer(Loot::MAIL, $rewards[$_id]['MailTemplateID']);
|
||||
// foreach ($mailSrc->iterate() as $loot)
|
||||
// $_curTpl['rewards'][] = [Type::ITEM, $loot['id']];
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class CreatureList extends DBTypeList
|
||||
$row3 = [Lang::game('level')];
|
||||
$fam = $this->curTpl['family'];
|
||||
|
||||
if (!($this->curTpl['typeFlags'] & 0x4))
|
||||
if (!($this->curTpl['typeFlags'] & NPC_TYPEFLAG_BOSS_MOB))
|
||||
{
|
||||
$level = $this->curTpl['minLevel'];
|
||||
if ($level != $this->curTpl['maxLevel'])
|
||||
@@ -154,6 +154,21 @@ class CreatureList extends DBTypeList
|
||||
return ($this->curTpl['cuFlags'] & NPC_CU_INSTANCE_BOSS) || ($this->curTpl['typeFlags'] & NPC_TYPEFLAG_BOSS_MOB && $this->curTpl['rank']);
|
||||
}
|
||||
|
||||
public function isMineable() : bool
|
||||
{
|
||||
return $this->curTpl['skinLootId'] && ($this->curTpl['typeFlags'] & NPC_TYPEFLAG_SKIN_WITH_MINING);
|
||||
}
|
||||
|
||||
public function isGatherable() : bool
|
||||
{
|
||||
return $this->curTpl['skinLootId'] && ($this->curTpl['typeFlags'] & NPC_TYPEFLAG_SKIN_WITH_HERBALISM);
|
||||
}
|
||||
|
||||
public function isSalvageable() : bool
|
||||
{
|
||||
return $this->curTpl['skinLootId'] && ($this->curTpl['typeFlags'] & NPC_TYPEFLAG_SKIN_WITH_ENGINEERING);
|
||||
}
|
||||
|
||||
public function getListviewData(int $addInfoMask = 0x0) : array
|
||||
{
|
||||
/* looks like this data differs per occasion
|
||||
|
||||
@@ -70,7 +70,7 @@ class GameObjectList extends DBTypeList
|
||||
$data[$this->id] = array(
|
||||
'id' => $this->id,
|
||||
'name' => Lang::unescapeUISequences($this->getField('name', true), Lang::FMT_RAW),
|
||||
'type' => $this->curTpl['typeCat'],
|
||||
'type' => $this->getField('typeCat'),
|
||||
'location' => $this->getSpawns(SPAWNINFO_ZONES)
|
||||
);
|
||||
|
||||
|
||||
@@ -1688,7 +1688,6 @@ class ItemList extends DBTypeList
|
||||
'level' => $this->curTpl['itemLevel'],
|
||||
'reqlevel' => $this->curTpl['requiredLevel'],
|
||||
'displayid' => $this->curTpl['displayId'],
|
||||
// 'commondrop' => 'true' / null // set if the item is a loot-filler-item .. check common ref-templates..?
|
||||
'holres' => $this->curTpl['resHoly'],
|
||||
'firres' => $this->curTpl['resFire'],
|
||||
'natres' => $this->curTpl['resNature'],
|
||||
@@ -2486,14 +2485,14 @@ class ItemListFilter extends Filter
|
||||
return null;
|
||||
|
||||
$refResults = [];
|
||||
$newRefs = DB::World()->selectCol('SELECT `entry` FROM ?# WHERE `item` = ?d AND `reference` = 0', LOOT_REFERENCE, $crs);
|
||||
$newRefs = DB::World()->selectCol('SELECT `entry` FROM ?# WHERE `item` = ?d AND `reference` = 0', Loot::REFERENCE, $crs);
|
||||
while ($newRefs)
|
||||
{
|
||||
$refResults += $newRefs;
|
||||
$newRefs = DB::World()->selectCol('SELECT `entry` FROM ?# WHERE `reference` IN (?a)', LOOT_REFERENCE, $newRefs);
|
||||
$newRefs = DB::World()->selectCol('SELECT `entry` FROM ?# WHERE `reference` IN (?a)', Loot::REFERENCE, $newRefs);
|
||||
}
|
||||
|
||||
$lootIds = DB::World()->selectCol('SELECT `entry` FROM ?# WHERE {`reference` IN (?a) OR }(`reference` = 0 AND `item` = ?d)', LOOT_DISENCHANT, $refResults ?: DBSIMPLE_SKIP, $crs);
|
||||
$lootIds = DB::World()->selectCol('SELECT `entry` FROM ?# WHERE {`reference` IN (?a) OR }(`reference` = 0 AND `item` = ?d)', Loot::DISENCHANT, $refResults ?: DBSIMPLE_SKIP, $crs);
|
||||
|
||||
return $lootIds ? ['disenchantId', $lootIds] : [0];
|
||||
}
|
||||
|
||||
@@ -385,20 +385,6 @@ define('MAX_LEVEL', 80);
|
||||
define('MAX_SKILL', 450);
|
||||
define('WOW_BUILD', 12340);
|
||||
|
||||
// Loot handles
|
||||
define('LOOT_FISHING', 'fishing_loot_template');
|
||||
define('LOOT_CREATURE', 'creature_loot_template');
|
||||
define('LOOT_GAMEOBJECT', 'gameobject_loot_template');
|
||||
define('LOOT_ITEM', 'item_loot_template');
|
||||
define('LOOT_DISENCHANT', 'disenchant_loot_template');
|
||||
define('LOOT_PROSPECTING', 'prospecting_loot_template');
|
||||
define('LOOT_MILLING', 'milling_loot_template');
|
||||
define('LOOT_PICKPOCKET', 'pickpocketing_loot_template');
|
||||
define('LOOT_SKINNING', 'skinning_loot_template');
|
||||
define('LOOT_MAIL', 'mail_loot_template'); // used by achievements and quests
|
||||
define('LOOT_SPELL', 'spell_loot_template');
|
||||
define('LOOT_REFERENCE', 'reference_loot_template');
|
||||
|
||||
// Sides
|
||||
define('SIDE_NONE', 0);
|
||||
define('SIDE_ALLIANCE', 1);
|
||||
|
||||
@@ -1,728 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
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
|
||||
*/
|
||||
/* 4.3 loot-example
|
||||
|
||||
template: 'item',
|
||||
id: 'drops',
|
||||
name: LANG.tab_drops,
|
||||
tabs: tabsRelated,
|
||||
parent: 'lkljbjkb574',
|
||||
extraCols: [Listview.extraCols.count, Listview.extraCols.percent],
|
||||
sort:['-percent', 'name'],
|
||||
_totalCount: 448092, // total # creature killed/looted
|
||||
computeDataFunc: Listview.funcBox.initLootTable,
|
||||
onAfterCreate: Listview.funcBox.addModeIndicator,
|
||||
data: [
|
||||
{
|
||||
"classs":15, // Tab Type
|
||||
"commondrop":true, // loot filtered as "not noteworthy"
|
||||
"id":25445,
|
||||
"level":1,
|
||||
"name":"7Wretched Ichor",
|
||||
"slot":0,
|
||||
"source":[2],
|
||||
"sourcemore":[{"z":3520}],
|
||||
"subclass":0, // Tab:Type
|
||||
modes:{
|
||||
"mode":4, // &1: heroic; &4: noteworthy(?); &8: reg10; &16: reg25; &32: hc10; &64: hc25; &128: RaidFinder
|
||||
"4":{"count":363318,"outof":448092} // calculate pct chance
|
||||
},
|
||||
count:363318,
|
||||
stack:[1,1],
|
||||
pctstack:'{1: 50.0123, 2: 49.9877}'
|
||||
}
|
||||
]
|
||||
});
|
||||
*/
|
||||
|
||||
|
||||
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 => [, $tabData])
|
||||
if ($tabData['data']) // only yield tabs with content
|
||||
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], true);
|
||||
|
||||
// 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_SKIN_WITH_HERBALISM)
|
||||
$tabId = 9;
|
||||
else if ($tabId < 0 && $curTpl['typeFlags'] & NPC_TYPEFLAG_SKIN_WITH_ENGINEERING)
|
||||
$tabId = 8;
|
||||
else if ($tabId < 0 && $curTpl['typeFlags'] & NPC_TYPEFLAG_SKIN_WITH_MINING)
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
71
includes/game/loot/loot.class.php
Normal file
71
includes/game/loot/loot.class.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
/* NOTE!
|
||||
*
|
||||
* TrinityCore uses "mode", a bitmask in a loot template, to distinguish dynamic properties of the template. i.e. entries get enabled if the boss fight progresses in a certain way.
|
||||
* WH/we uses "mode", different loot templates, to describe the raid/dungeon difficulty
|
||||
*
|
||||
* try to not mix this shit up
|
||||
*/
|
||||
|
||||
abstract class Loot
|
||||
{
|
||||
// Loot handles
|
||||
public const FISHING = 'fishing_loot_template'; // fishing_loot_template (no relation entry is linked with ID of the fishing zone or area)
|
||||
public const CREATURE = 'creature_loot_template'; // creature_loot_template entry many <- many creature_template lootid
|
||||
public const GAMEOBJECT = 'gameobject_loot_template'; // gameobject_loot_template entry many <- many gameobject_template data1 (see its lockType for mining, herbing, fishing or generic looting)
|
||||
public const ITEM = 'item_loot_template'; // item_loot_template entry many <- one item_template entry
|
||||
public const DISENCHANT = 'disenchant_loot_template'; // disenchant_loot_template entry many <- many item_template DisenchantID
|
||||
public const PROSPECTING = 'prospecting_loot_template'; // prospecting_loot_template entry many <- one item_template entry
|
||||
public const MILLING = 'milling_loot_template'; // milling_loot_template entry many <- one item_template entry
|
||||
public const PICKPOCKET = 'pickpocketing_loot_template'; // pickpocketing_loot_template entry many <- many creature_template pickpocketloot
|
||||
public const SKINNING = 'skinning_loot_template'; // skinning_loot_template entry many <- many creature_template skinloot (see the creatures flags for mining, herbing, salvaging or actual skinning)
|
||||
public const MAIL = 'mail_loot_template'; // mail_loot_template entry quest_template RewMailTemplateId (quest + achievement)
|
||||
public const SPELL = 'spell_loot_template'; // spell_loot_template entry many <- one spell.dbc id
|
||||
public const REFERENCE = 'reference_loot_template'; // reference_loot_template entry many <- many *_loot_template reference
|
||||
|
||||
protected const TEMPLATES = [self::REFERENCE, self::ITEM, self::DISENCHANT, self::PROSPECTING, self::CREATURE, self::MILLING, self::PICKPOCKET, self::SKINNING, self::FISHING, self::GAMEOBJECT, self::MAIL, self::SPELL];
|
||||
|
||||
public array $jsGlobals = [];
|
||||
|
||||
protected array $results = [];
|
||||
|
||||
/**
|
||||
* builds stack info string for listview rows
|
||||
* issue: TC always has an equal distribution between min/max
|
||||
* and yes, it wants a string .. how weired is that..
|
||||
*
|
||||
* @param int $min min amount
|
||||
* @param int $max max amount
|
||||
* @return ?string stack info or null on error
|
||||
*/
|
||||
protected static function buildStack(int $min, int $max) : ?string
|
||||
{
|
||||
if (!$min || !$max || $max <= $min)
|
||||
return null;
|
||||
|
||||
$stack = [];
|
||||
for ($i = $min; $i <= $max; $i++)
|
||||
$stack[$i] = round(100 / (1 + $max - $min), 3);
|
||||
|
||||
// do not replace with Util::toJSON !
|
||||
return json_encode($stack, JSON_NUMERIC_CHECK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data js global data to store
|
||||
* @return void
|
||||
*/
|
||||
protected function storeJSGlobals(array $data) : void
|
||||
{
|
||||
Util::mergeJsGlobals($this->jsGlobals, $data);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
329
includes/game/loot/lootbycontainer.class.php
Normal file
329
includes/game/loot/lootbycontainer.class.php
Normal file
@@ -0,0 +1,329 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
use stdClass;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
class LootByContainer extends Loot
|
||||
{
|
||||
public array $extraCols = [];
|
||||
|
||||
private array $knownRefs = []; // known ref loot results (can be reused)
|
||||
|
||||
/**
|
||||
* @return array found loot result
|
||||
*/
|
||||
public function getResult() : array
|
||||
{
|
||||
return $this->results;
|
||||
}
|
||||
|
||||
/**
|
||||
* recurse through reference loot while applying modifiers from parent container
|
||||
*
|
||||
* @param string $tableName a known loot template table name
|
||||
* @param int $lootId a loot template entry
|
||||
* @param int $groupId [optional] limit result to provided loot group
|
||||
* @param float $baseChance [optional] chance multiplier passed down from parent container
|
||||
* @return array [[<array>lootRows], [<int>itemIds]]
|
||||
*/
|
||||
private function getByContainerRecursive(string $tableName, int $lootId, 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 == self::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], true);
|
||||
|
||||
// TC 'mode' (dynamic loot modifier)
|
||||
$buff = [];
|
||||
for ($i = 0; $i < 8; $i++)
|
||||
if ($entry['LootMode'] & (1 << $i))
|
||||
$buff[] = $i + 1;
|
||||
|
||||
$set['mode'] = implode(', ', $buff);
|
||||
|
||||
if ($entry['Reference'])
|
||||
{
|
||||
if (!in_array($entry['Reference'], $this->knownRefs))
|
||||
$this->knownRefs[$entry['Reference']] = $this->getByContainerRecursive(self::REFERENCE, $entry['Reference'], 0, $entry['Chance'] / 100);
|
||||
|
||||
[$data, $raw] = $this->knownRefs[$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())
|
||||
{
|
||||
$this->storeJSGlobals($cnd->getJsGlobals());
|
||||
$cnd->toListviewColumn($loot, $this->extraCols, $lootId, 'content');
|
||||
}
|
||||
|
||||
return [$loot, array_unique($rawItems)];
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch loot for given loot container and optionally merge multiple container while adding mode info.
|
||||
* If difficultyBit is 0, no merge will occur
|
||||
*
|
||||
* @param string $table a known loote template table name
|
||||
* @param array $lootEntries array of [difficultyBit => entry].
|
||||
* @return bool success and found loot
|
||||
*/
|
||||
public function getByContainer(string $table, array $lootEntries): bool
|
||||
{
|
||||
if (!in_array($table, self::TEMPLATES))
|
||||
return false;
|
||||
|
||||
foreach ($lootEntries as $modeBit => $entry)
|
||||
{
|
||||
if (!$entry)
|
||||
continue;
|
||||
|
||||
[$lootRows, $itemIds] = $this->getByContainerRecursive($table, $entry);
|
||||
if (!$lootRows)
|
||||
continue;
|
||||
|
||||
$items = new ItemList(array(['i.id', $itemIds], Cfg::get('SQL_LIMIT_NONE')));
|
||||
$this->storeJSGlobals($items->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED));
|
||||
$itemRows = $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)
|
||||
{
|
||||
$count = ceil($loot['groupChance'] * $loot['realChanceMod'] * 100);
|
||||
|
||||
/* on modes...
|
||||
* modes.mode is the (masked) sum of all modes where this item has been seen
|
||||
* modes.mode & 1 dungeon normal
|
||||
* modes.mode & 2 dungeon heroic
|
||||
* modes.mode & 4 generic case (never included in mask for instanced creatures/gos or always === 4 for non-instanced creatures/gos)
|
||||
* modes.mode & 8 raid 10 nh
|
||||
* modes.mode & 16 raid 25 nh
|
||||
* modes.mode & 32 raid 10 hc
|
||||
* modes.mode & 64 raid 25 hc
|
||||
*
|
||||
* modes[4] is _always_ included and is the sum total over all modes:
|
||||
* ex: modes:{"mode":1,"1":{"count":4408,"outof":16013},"4":{"count":4408,"outof":22531}}
|
||||
*/
|
||||
if ($modeBit)
|
||||
{
|
||||
$modes = array( // emulate 'percent' with precision: 2
|
||||
'mode' => $modeBit,
|
||||
$modeBit => ['count' => $count, 'outof' => 10000]
|
||||
);
|
||||
if ($modeBit != 4)
|
||||
$modes[4] = $modes[$modeBit];
|
||||
|
||||
|
||||
// unsure: force display as noteworthy
|
||||
// if (!empty($loot['content']) && !empty($itemRows[$loot['content']]) && $itemRows[$loot['content']]['name'][0] == 7 - ITEM_QUALITY_POOR)
|
||||
// $modes['mode'] = 4;
|
||||
// else if ($count < 100) // chance < 1%
|
||||
// $modes['mode'] = 4;
|
||||
|
||||
|
||||
// existing result row; merge modes and move on
|
||||
if (!is_null($k = array_find_key($this->results, function($x) use ($loot) {
|
||||
if (!empty($loot['reference']))
|
||||
return $x['id'] == $loot['reference'] && $x['mode'] == $loot['mode'] && $x['group'] == $loot['group'] && $x['stack'] == [$loot['multiplier'], $loot['multiplier']];
|
||||
else
|
||||
return $x['id'] == $loot['content'] && $x['mode'] == $loot['mode'] && $x['group'] == $loot['group'];
|
||||
})))
|
||||
{
|
||||
$this->results[$k]['modes']['mode'] |= $modes['mode'];
|
||||
$this->results[$k]['modes'][$modeBit] = $modes[$modeBit];
|
||||
$this->results[$k]['modes'][4]['count'] = max($modes[4]['count'], $this->results[$k]['modes'][4]['count']);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$base = array(
|
||||
'count' => $count,
|
||||
'outof' => 10000,
|
||||
'group' => $loot['group'],
|
||||
'quest' => $loot['quest'],
|
||||
'mode' => $loot['mode'] ?: null, // dyn loot mode
|
||||
'modes' => $modes ?? null, // difficulties
|
||||
'reference' => $loot['parentRef'] ?: null,
|
||||
'condition' => $loot['condition'] ?? null,
|
||||
'pctstack' => self::buildStack($loot['min'] ?? 0, $loot['max'] ?? 0)
|
||||
);
|
||||
|
||||
$base = array_filter($base, fn($x) => $x !== null);
|
||||
|
||||
if (empty($loot['reference'])) // regular drop
|
||||
{
|
||||
if ($itemRow = $itemRows[$loot['content']] ?? null)
|
||||
{
|
||||
$extra = ['stack' => [$loot['min'], $loot['max']]];
|
||||
|
||||
// unsure if correct - tag item as trash if chance < 1% and tagged as having many sources
|
||||
if ($base['count'] < 100 && $items->getEntry($loot['content'])['moreMask'] & SRC_FLAG_COMMON)
|
||||
$extra['commondrop'] = 1;
|
||||
|
||||
if (!User::isInGroup(U_GROUP_EMPLOYEE))
|
||||
{
|
||||
if (!isset($this->results[$loot['content']]))
|
||||
$this->results[$loot['content']] = array_merge($itemRow, $base, $extra);
|
||||
else
|
||||
$this->results[$loot['content']]['count'] += $base['count'];
|
||||
}
|
||||
else
|
||||
$this->results[] = array_merge($itemRow, $base, $extra);
|
||||
}
|
||||
else
|
||||
trigger_error('Item #'.$loot['content'].' referenced by loot does not exist!', E_USER_WARNING);
|
||||
}
|
||||
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 &$_)
|
||||
{
|
||||
// remember 'count' is always relative to a base of 10000
|
||||
if ($_['count'] <= 10000)
|
||||
continue;
|
||||
|
||||
while ($_['count'] > 20000)
|
||||
{
|
||||
$_['stack'][0]++;
|
||||
$_['stack'][1]++;
|
||||
$_['count'] -= 10000;
|
||||
}
|
||||
|
||||
$_['stack'][1]++;
|
||||
$_['count'] = 10000;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$fields = [['mode', 'Dyn. Mode'], ['reference', 'Reference']];
|
||||
$base = [];
|
||||
$set = 0;
|
||||
foreach ($this->results as $foo)
|
||||
{
|
||||
foreach ($fields as $idx => [$field, $title])
|
||||
{
|
||||
$val = $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, $title])
|
||||
if ($set & (1 << $idx))
|
||||
$this->extraCols[] = "\$Listview.funcBox.createSimpleCol('".$field."', '".$title."', '7%', '".$field."')";
|
||||
}
|
||||
|
||||
return !empty($this->results);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
439
includes/game/loot/lootbyitem.class.php
Normal file
439
includes/game/loot/lootbyitem.class.php
Normal file
@@ -0,0 +1,439 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
class LootByItem extends Loot
|
||||
{
|
||||
public const /* int */ ITEM_CONTAINED = 0;
|
||||
public const /* int */ ITEM_DISENCHANTED = 1;
|
||||
public const /* int */ ITEM_PROSPECTED = 2;
|
||||
public const /* int */ ITEM_MILLED = 3;
|
||||
public const /* int */ NPC_DROPPED = 4;
|
||||
public const /* int */ NPC_PICKPOCKETED = 5;
|
||||
public const /* int */ NPC_SKINNED = 6;
|
||||
public const /* int */ NPC_MINED = 7;
|
||||
public const /* int */ NPC_SALVAGED = 8;
|
||||
public const /* int */ NPC_GATHERED = 9;
|
||||
public const /* int */ QUEST_REWARD = 10;
|
||||
public const /* int */ ZONE_FISHED = 11;
|
||||
public const /* int */ OBJECT_CONTAINED = 12;
|
||||
public const /* int */ OBJECT_MINED = 13;
|
||||
public const /* int */ OBJECT_GATHERED = 14;
|
||||
public const /* int */ OBJECT_FISHED = 15;
|
||||
public const /* int */ SPELL_CREATED = 16;
|
||||
public const /* int */ ACHIEVEMENT_REWARD = 17;
|
||||
|
||||
private array $chanceMods = [];
|
||||
private array $listviewTabs = array( // order here determines tab order on page
|
||||
// [fileName, tabData, tabName, tabId, extraCols, hiddenCols, visibleCols]
|
||||
self::NPC_DROPPED => [Type::NPC, [], '$LANG.tab_droppedby', 'dropped-by', [], [], []],
|
||||
self::QUEST_REWARD => [Type::QUEST, [], '$LANG.tab_rewardfrom', 'reward-from-quest', [], [], []],
|
||||
self::ITEM_CONTAINED => [Type::ITEM, [], '$LANG.tab_containedin', 'contained-in-item', [], [], []],
|
||||
self::OBJECT_CONTAINED => [Type::OBJECT, [], '$LANG.tab_containedin', 'contained-in-object', [], [], []],
|
||||
self::NPC_PICKPOCKETED => [Type::NPC, [], '$LANG.tab_pickpocketedfrom', 'pickpocketed-from', [], [], []],
|
||||
self::NPC_SKINNED => [Type::NPC, [], '$LANG.tab_skinnedfrom', 'skinned-from', [], [], []],
|
||||
self::ITEM_DISENCHANTED => [Type::ITEM, [], '$LANG.tab_disenchantedfrom', 'disenchanted-from', [], [], []],
|
||||
self::ITEM_PROSPECTED => [Type::ITEM, [], '$LANG.tab_prospectedfrom', 'prospected-from', [], [], []],
|
||||
self::ITEM_MILLED => [Type::ITEM, [], '$LANG.tab_milledfrom', 'milled-from', [], [], []],
|
||||
self::NPC_MINED => [Type::NPC, [], '$LANG.tab_minedfromnpc', 'mined-from-npc', [], [], []],
|
||||
self::NPC_SALVAGED => [Type::NPC, [], '$LANG.tab_salvagedfrom', 'salvaged-from', [], [], []],
|
||||
self::NPC_GATHERED => [Type::NPC, [], '$LANG.tab_gatheredfromnpc', 'gathered-from-npc', [], [], []],
|
||||
self::OBJECT_MINED => [Type::OBJECT, [], '$LANG.tab_minedfrom', 'mined-from-object', [], [], []],
|
||||
self::OBJECT_GATHERED => [Type::OBJECT, [], '$LANG.tab_gatheredfrom', 'gathered-from-object', [], [], []],
|
||||
self::ZONE_FISHED => [Type::ZONE, [], '$LANG.tab_fishedin', 'fished-in-zone', [], [], []],
|
||||
self::OBJECT_FISHED => [Type::OBJECT, [], '$LANG.tab_fishedin', 'fished-in-object', [], [], []],
|
||||
self::SPELL_CREATED => [Type::SPELL, [], '$LANG.tab_createdby', 'created-by', [], [], []],
|
||||
self::ACHIEVEMENT_REWARD => [Type::ACHIEVEMENT, [], '$LANG.tab_rewardfrom', 'reward-from-achievement', [], [], []]
|
||||
);
|
||||
private string $queryTemplate =
|
||||
'SELECT lt1.`entry` AS ARRAY_KEY,
|
||||
IF(lt1.`reference` = 0, lt1.`item`, lt1.`reference`) AS "item",
|
||||
lt1.`chance` AS "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`';
|
||||
|
||||
/**
|
||||
* @param int $entry item id to find loot container for
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(private int $entry)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* iterate over result set
|
||||
*
|
||||
* @return iterable [tabIdx => [lvTemplate, lvData]]
|
||||
*/
|
||||
public function &iterate() : \Generator
|
||||
{
|
||||
reset($this->results);
|
||||
|
||||
foreach ($this->results as $k => [, $tabData])
|
||||
if ($tabData['data']) // only yield tabs with content
|
||||
yield $k => $this->results[$k];
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate chance and stack info and apply to loot rows
|
||||
*
|
||||
* @param array $refs loot rows to apply chance + stack info to
|
||||
* @param array $parents [optional] ref loot ids this call is derived from
|
||||
* @return array [entry => stack+chance-info]
|
||||
*/
|
||||
private function calcChance(array $refs, array $parents = []) : array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
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))
|
||||
continue;
|
||||
|
||||
$result[$rId] = array(
|
||||
'percent' => $chance,
|
||||
'stack' => [$ref['min'], $ref['max']],
|
||||
'count' => 1, // ..and one for the sort script
|
||||
'pctstack' => self::buildStack($ref['min'], $ref['max'])
|
||||
);
|
||||
}
|
||||
|
||||
// sort by % DESC
|
||||
uasort($result, fn($a, $b) => $b['percent'] <=> $a['percent']);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch loot container for item provided to __construct
|
||||
*
|
||||
* @param int $maxResults [optional] SQL_LIMIT override
|
||||
* @param array $lootTableList [optional] limit lookup to provided loot template table names
|
||||
* @return bool success
|
||||
*/
|
||||
public function getByItem(int $maxResults = -1, array $lootTableList = []) : bool
|
||||
{
|
||||
if (!$this->entry)
|
||||
return false;
|
||||
|
||||
if ($maxResults < 0)
|
||||
$maxResults = Cfg::get('SQL_LIMIT_DEFAULT');
|
||||
|
||||
$refResults = [];
|
||||
|
||||
/*
|
||||
get references containing the item
|
||||
*/
|
||||
$newRefs = DB::World()->select(
|
||||
sprintf($this->queryTemplate, '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))
|
||||
$this->storejsGlobals($cnd->getJsGlobals());
|
||||
}
|
||||
*/
|
||||
|
||||
while ($newRefs)
|
||||
{
|
||||
$curRefs = $newRefs;
|
||||
$newRefs = DB::World()->select(
|
||||
sprintf($this->queryTemplate, '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 refs
|
||||
*/
|
||||
foreach (self::TEMPLATES as $lootTemplate)
|
||||
{
|
||||
if ($lootTableList && !in_array($lootTemplate, $lootTableList))
|
||||
continue;
|
||||
|
||||
if ($lootTemplate == Loot::REFERENCE)
|
||||
continue;
|
||||
|
||||
$result = $this->calcChance(DB::World()->select(
|
||||
sprintf($this->queryTemplate, '{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) and (herb, ore, leather)
|
||||
$ids = array_slice(array_keys($result), 0, $maxResults);
|
||||
|
||||
// fill ListviewTabs
|
||||
match ($lootTemplate)
|
||||
{
|
||||
Loot::GAMEOBJECT => $this->handleObjectLoot( $ids, $result),
|
||||
Loot::MAIL => $this->handleMailLoot( $ids, $result),
|
||||
Loot::SPELL => $this->handleSpellLoot( $ids, $result),
|
||||
Loot::CREATURE => $this->handleNpcLoot( $ids, $result, self::NPC_DROPPED, 'lootId'),
|
||||
Loot::PICKPOCKET => $this->handleNpcLoot( $ids, $result, self::NPC_PICKPOCKETED, 'pickpocketLootId'),
|
||||
Loot::SKINNING => $this->handleNpcLoot( $ids, $result, self::NPC_SKINNED, 'skinLootId'), // tabId < 0: assigned real id later
|
||||
Loot::PROSPECTING => $this->handleGenericLoot($ids, $result, self::ITEM_PROSPECTED, 'id'),
|
||||
Loot::MILLING => $this->handleGenericLoot($ids, $result, self::ITEM_MILLED, 'id'),
|
||||
Loot::ITEM => $this->handleGenericLoot($ids, $result, self::ITEM_CONTAINED, 'id'),
|
||||
Loot::DISENCHANT => $this->handleGenericLoot($ids, $result, self::ITEM_DISENCHANTED, 'disenchantId'),
|
||||
Loot::FISHING => $this->handleGenericLoot($ids, $result, self::ZONE_FISHED, 'id') // subAreas are currently ignored
|
||||
};
|
||||
}
|
||||
|
||||
// finalize tabs
|
||||
foreach ($this->listviewTabs as $idx => [$type, $data, $name, $id, $extraCols, $hiddenCols, $visibleCols])
|
||||
{
|
||||
$tabData = array(
|
||||
'data' => $data,
|
||||
'name' => $name,
|
||||
'id' => $id
|
||||
);
|
||||
|
||||
if ($extraCols)
|
||||
$tabData['extraCols'] = array_unique($extraCols);
|
||||
|
||||
if ($hiddenCols)
|
||||
$tabData['hiddenCols'] = array_unique($hiddenCols);
|
||||
|
||||
if ($visibleCols)
|
||||
$tabData['visibleCols'] = array_unique($visibleCols);
|
||||
|
||||
$this->results[$idx] = [Type::getFileString($type), $tabData];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function handleGenericLoot(array $ids, array $result, int $tabId, string $dbField) : bool
|
||||
{
|
||||
if (!$ids)
|
||||
return false;
|
||||
|
||||
[$type, &$data, , , &$extraCols, ,] = $this->listviewTabs[$tabId];
|
||||
|
||||
$srcObj = Type::newList($type, array([$dbField, $ids]));
|
||||
if (!$srcObj || $srcObj->error)
|
||||
return false;
|
||||
|
||||
$srcData = $srcObj->getListviewData();
|
||||
$this->storeJSGlobals($srcObj->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED));
|
||||
|
||||
$extraCols[] = '$Listview.extraCols.percent';
|
||||
|
||||
foreach ($srcObj->iterate() as $__)
|
||||
$data[] = array_merge($srcData[$srcObj->id], $result[$srcObj->getField($dbField)]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function handleNpcLoot(array $ids, array $result, int $tabId, string $dbField) : bool
|
||||
{
|
||||
if (!$ids)
|
||||
return false;
|
||||
|
||||
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)
|
||||
{
|
||||
$this->storeJSGlobals($parentObj->getJSGlobals());
|
||||
$parentData = $parentObj->getListviewData();
|
||||
$ids = array_diff($ids, $baseIds);
|
||||
}
|
||||
}
|
||||
|
||||
$npc = new CreatureList(array([$dbField, $ids]));
|
||||
if ($npc->error)
|
||||
return false;
|
||||
|
||||
$srcData = $npc->getListviewData();
|
||||
$this->storeJSGlobals($npc->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED));
|
||||
[, &$data, , , &$extraCols, ,] = $this->listviewTabs[$tabId];
|
||||
|
||||
foreach ($npc->iterate() as $__)
|
||||
{
|
||||
if ($tabId == self::NPC_SKINNED)
|
||||
{
|
||||
if ($npc->isMineable())
|
||||
$tabId = self::NPC_MINED;
|
||||
else if ($npc->isGatherable())
|
||||
$tabId = self::NPC_GATHERED;
|
||||
else if ($npc->isSalvageable())
|
||||
$tabId = self::NPC_SALVAGED;
|
||||
}
|
||||
|
||||
$p = $npc->getField('parentId');
|
||||
|
||||
$data[] = array_merge($parentData[$p] ?? $srcData[$npc->id], $result[$npc->getField($dbField)]);
|
||||
$extraCols[] = '$Listview.extraCols.percent';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function handleSpellLoot(array $ids, array $result) : bool
|
||||
{
|
||||
$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)
|
||||
return false;
|
||||
|
||||
$this->storeJSGlobals($srcObj->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED));
|
||||
[, &$data, , , &$extraCols, , &$visibleCols] = $this->listviewTabs[self::SPELL_CREATED];
|
||||
|
||||
if (!empty($result))
|
||||
$extraCols[] = '$Listview.extraCols.percent';
|
||||
|
||||
if ($srcObj->hasSetFields('reagent1', 'reagent2', 'reagent3', 'reagent4', 'reagent5', 'reagent6', 'reagent7', 'reagent8'))
|
||||
$visibleCols[] = 'reagents';
|
||||
|
||||
foreach ($srcObj->getListviewData() as $id => $row)
|
||||
$data[] = array_merge($row, $result[$id] ?? ['percent' => -1]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function handleMailLoot(array $ids, array $result) : bool
|
||||
{
|
||||
// quest part
|
||||
$conditions = array('OR',
|
||||
['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]
|
||||
);
|
||||
if ($ids)
|
||||
$conditions[] = ['rewardMailTemplateId', $ids];
|
||||
|
||||
$quests = new QuestList($conditions);
|
||||
if (!$quests->error)
|
||||
{
|
||||
$this->storeJSGlobals($quests->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_REWARDS));
|
||||
[, &$qData, , , , , ] = $this->listviewTabs[self::QUEST_REWARD];
|
||||
|
||||
foreach ($quests->getListviewData() as $id => $row)
|
||||
$qData[] = array_merge($row, $result[$id] ?? ['percent' => -1]);
|
||||
}
|
||||
|
||||
// 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');
|
||||
|
||||
$achievements = new AchievementList($conditions);
|
||||
if (!$achievements->error)
|
||||
{
|
||||
$this->storeJSGlobals($achievements->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_REWARDS));
|
||||
[, &$aData, , , , &$hiddenCols, &$visibleCols] = $this->listviewTabs[self::ACHIEVEMENT_REWARD];
|
||||
|
||||
foreach ($achievements->getListviewData() as $id => $row)
|
||||
$aData[] = array_merge($row, $result[$id] ?? ['percent' => -1]);
|
||||
|
||||
$hiddenCols[] = 'rewards';
|
||||
$visibleCols[] = 'category';
|
||||
}
|
||||
|
||||
return !$quests->error || !$achievements->error;
|
||||
}
|
||||
|
||||
private function handleObjectLoot(array $ids, array $result) : bool
|
||||
{
|
||||
if (!$ids)
|
||||
return false;
|
||||
|
||||
$srcObj = new GameObjectList(array(['lootId', $ids]));
|
||||
if ($srcObj->error)
|
||||
return false;
|
||||
|
||||
foreach ($srcObj->getListviewData() as $id => $row)
|
||||
{
|
||||
$tabId = match($row['type'])
|
||||
{
|
||||
25 => self::OBJECT_FISHED, // fishing node
|
||||
-3 => self::OBJECT_GATHERED, // herb
|
||||
-4 => self::OBJECT_MINED, // vein
|
||||
default => self::OBJECT_CONTAINED // general chest loot
|
||||
};
|
||||
|
||||
[, &$tabData, , , &$extraCols, , &$visibleCols] = $this->listviewTabs[$tabId];
|
||||
|
||||
$tabData[] = array_merge($row, $result[$srcObj->getEntry($id)['lootId']]);
|
||||
$extraCols[] = '$Listview.extraCols.percent';
|
||||
if ($tabId != 15)
|
||||
$visibleCols[] = 'skill';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -54,6 +54,8 @@ spl_autoload_register(function (string $class) : void
|
||||
require_once 'includes/game/chrstatistics.php';
|
||||
else if (file_exists('includes/game/'.strtolower($class).'.class.php'))
|
||||
require_once 'includes/game/'.strtolower($class).'.class.php';
|
||||
else if (file_exists('includes/game/loot/'.strtolower($class).'.class.php'))
|
||||
require_once 'includes/game/loot/'.strtolower($class).'.class.php';
|
||||
});
|
||||
|
||||
// our site components
|
||||
|
||||
@@ -6,6 +6,27 @@ if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
|
||||
// PHP 8.4 polyfill
|
||||
if (version_compare(PHP_VERSION, '8.4.0') < 0)
|
||||
{
|
||||
function array_find(array $array, callable $callback) : mixed
|
||||
{
|
||||
foreach ($array as $k => $v)
|
||||
if ($callback($v, $k))
|
||||
return $array[$k];
|
||||
return null;
|
||||
}
|
||||
|
||||
function array_find_key(array $array, callable $callback) : mixed
|
||||
{
|
||||
foreach ($array as $k => $v)
|
||||
if ($callback($v, $k))
|
||||
return $k;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleXML extends \SimpleXMLElement
|
||||
{
|
||||
public function addCData(string $cData) : \SimpleXMLElement
|
||||
|
||||
@@ -397,7 +397,11 @@ $lang = array(
|
||||
|
||||
'phases' => "Phasen",
|
||||
'mode' => "Modus: ",
|
||||
'modes' => [-1 => "Beliebig", "Normal / Normal 10", "Heroisch / Normal 25", "Heroisch 10", "Heroisch 25"],
|
||||
'modes' => array(
|
||||
[-1 => "Beliebig", "Normal / Normal 10", "Heroisch / Normal 25", "Heroisch 10", "Heroisch 25"],
|
||||
["Normal", "Heroisch"],
|
||||
["Normal 10", "Normal 25", "Heroisch 10", "Heroisch 25"]
|
||||
),
|
||||
'expansions' => ["Classic", "The Burning Crusade", "Wrath of the Lich King"],
|
||||
'stats' => ["Stärke", "Beweglichkeit", "Ausdauer", "Intelligenz", "Willenskraft"],
|
||||
'timeAbbrev' => array(
|
||||
@@ -1208,10 +1212,6 @@ $lang = array(
|
||||
'mechanicimmune'=> 'Nicht anfällig für Mechanik: %s',
|
||||
'_extraFlags' => 'Extra Flags: ',
|
||||
'versions' => 'Schwierigkeitsgrade: ',
|
||||
'modes' => array(
|
||||
1 => ["Normal", "Heroisch"],
|
||||
2 => ["10-Spieler Normal", "25-Spieler Normal", "10-Spieler Heroisch", "25-Spieler Heroisch"]
|
||||
),
|
||||
'cat' => array(
|
||||
"Nicht kategorisiert", "Wildtiere", "Drachkin", "Dämonen", "Elementare", "Riesen", "Untote", "Humanoide",
|
||||
"Tiere", "Mechanisch", "Nicht spezifiziert", "Totems", "Haustiere", "Gaswolken"
|
||||
|
||||
@@ -397,7 +397,11 @@ $lang = array(
|
||||
|
||||
'phases' => "Phases",
|
||||
'mode' => "Mode: ",
|
||||
'modes' => [-1 => "Any", "Normal / Normal 10", "Heroic / Normal 25", "Heroic 10", "Heroic 25"],
|
||||
'modes' => array(
|
||||
[-1 => "Any", "Normal / Normal 10", "Heroic / Normal 25", "Heroic 10", "Heroic 25"],
|
||||
["Normal", "Heroic"],
|
||||
["Normal 10", "Normal 25", "Heroic 10", "Heroic 25"]
|
||||
),
|
||||
'expansions' => ["Classic", "The Burning Crusade", "Wrath of the Lich King"],
|
||||
'stats' => ["Strength", "Agility", "Stamina", "Intellect", "Spirit"],
|
||||
'timeAbbrev' => array( // <time>S_ABBR
|
||||
@@ -1208,10 +1212,6 @@ $lang = array(
|
||||
'mechanicimmune'=> 'Not affected by mechanic: %s',
|
||||
'_extraFlags' => 'Extra Flags: ',
|
||||
'versions' => 'Difficulty Versions: ',
|
||||
'modes' => array(
|
||||
1 => ["Normal", "Heroic"],
|
||||
2 => ["10-player Normal", "25-player Normal", "10-player Heroic", "25-player Heroic"]
|
||||
),
|
||||
'cat' => array(
|
||||
"Uncategorized", "Beasts", "Dragonkins", "Demons", "Elementals", "Giants", "Undead", "Humanoids",
|
||||
"Critters", "Mechanicals", "Not specified", "Totems", "Non-combat Pets", "Gas Clouds"
|
||||
|
||||
@@ -397,7 +397,11 @@ $lang = array(
|
||||
|
||||
'phases' => "Fases",
|
||||
'mode' => "Modo: ",
|
||||
'modes' => [-1 => "Cualquiera", "Normal / Normal 10", "Heroico / Normal 25", "Heróico 10", "Heróico 25"],
|
||||
'modes' => array(
|
||||
[-1 => "Cualquiera", "Normal / Normal 10", "Heroico / Normal 25", "Heróico 10", "Heróico 25"],
|
||||
["Normal", "Heroico"],
|
||||
["Normal 10", "Normal 25", "Heróico 10", "Heróico 25"],
|
||||
),
|
||||
'expansions' => ["World of Warcraft", "The Burning Crusade", "Wrath of the Lich King"],
|
||||
'stats' => ["Fuerza", "Agilidad", "Aguante", "Intelecto", "Espíritu"],
|
||||
'timeAbbrev' => array(
|
||||
@@ -1208,10 +1212,6 @@ $lang = array(
|
||||
'mechanicimmune'=> 'No afectado por la mecánica: %s',
|
||||
'_extraFlags' => 'Banderas extra: ',
|
||||
'versions' => 'Versiones de dificultad: ',
|
||||
'modes' => array(
|
||||
1 => ["Normal", "Heroico"],
|
||||
2 => ["10 jugadores Normal", "25 jugadores Normal", "10 jugadores Heroico", "25 jugadores Heroico"]
|
||||
),
|
||||
'cat' => array(
|
||||
"Sin categoría", "Bestia", "Dragonante", "Demonio", "Elemental", "Gigante", "No-muerto", "Humanoide",
|
||||
"Alimaña", "Mecánico", "Sin especificar", "Tótem", "Mascota mansa", "Nube de gas"
|
||||
|
||||
@@ -397,7 +397,11 @@ $lang = array(
|
||||
|
||||
'phases' => "Phases",
|
||||
'mode' => "Mode : ",
|
||||
'modes' => [-1 => "Tout", "Standard / Normal 10", "Héroïque / Normal 25", "10 héroïque", "25 héroïque"],
|
||||
'modes' => array(
|
||||
[-1 => "Tout", "Standard / Normal 10", "Héroïque / Normal 25", "10 Héroïque", "25 Héroïque"],
|
||||
["Normal", "Héroïque"],
|
||||
["10 Normal", "25 Normal", "10 Héroïque", "25 Héroïque"],
|
||||
),
|
||||
'expansions' => ["Classique", "The Burning Crusade", "Wrath of the Lich King"],
|
||||
'stats' => ["Force", "Agilité", "Endurance", "Intelligence", "Esprit"],
|
||||
'timeAbbrev' => array(
|
||||
@@ -1208,10 +1212,6 @@ $lang = array(
|
||||
'mechanicimmune'=> '[Not affected by mechanic] : %s',
|
||||
'_extraFlags' => '[Extra Flags] : ',
|
||||
'versions' => '[Difficulty Versions] : ',
|
||||
'modes' => array(
|
||||
1 => ["Normal", "Héroïque"],
|
||||
2 => ["10-joueurs Normal", "25-joueurs Normal", "10-joueurs Héroïque", "25-joueurs Héroïque"]
|
||||
),
|
||||
'cat' => array(
|
||||
"Non classés", "Bêtes", "Draconien", "Démons", "Élémentaires", "Géants", "Mort-vivant", "Humanoïdes",
|
||||
"Bestioles", "Mécaniques", "Non spécifié", "Totems", "Familier pacifique", "Nuages de gaz"
|
||||
|
||||
@@ -397,7 +397,11 @@ $lang = array(
|
||||
|
||||
'phases' => "Фазы",
|
||||
'mode' => "Режим: ",
|
||||
'modes' => [-1 => "Все", "Обычный / 10-норм.", "Героический / 25-норм.", "10-героич", "25-героич"],
|
||||
'modes' => array(
|
||||
[-1 => "Все", "Обычный / 10-норм.", "Героический / 25-норм.", "10-героич", "25-героич"],
|
||||
["Обычный", "Героический"],
|
||||
["10-нормал", "25-нормал", "10-героич", "25-героич"],
|
||||
),
|
||||
'expansions' => array("World of Warcraft", "The Burning Crusade", "Wrath of the Lich King"),
|
||||
'stats' => array("к силе", "к ловкости", "к выносливости", "к интеллекту", "к духу"),
|
||||
'timeAbbrev' => array(
|
||||
@@ -1208,10 +1212,6 @@ $lang = array(
|
||||
'mechanicimmune'=> '[Not affected by mechanic]: %s',
|
||||
'_extraFlags' => '[Extra Flags]: ',
|
||||
'versions' => '[Difficulty Versions]: ',
|
||||
'modes' => array(
|
||||
1 => ["Обычный", "Героический"],
|
||||
2 => ["10 нормал.", "25 нормал.", "10 героич.", "25 героич."]
|
||||
),
|
||||
'cat' => array(
|
||||
"Разное", "Животные", "Дракон", "Демоны", "Элементали", "Великаны", "Нежить", "Гуманоиды",
|
||||
"Существа", "Механизмы", "Не указано", "Тотемы", "Спутники", "Облака газа"
|
||||
|
||||
@@ -397,7 +397,11 @@ $lang = array(
|
||||
|
||||
'phases' => "阶段",
|
||||
'mode' => "模式:",
|
||||
'modes' => [-1 => "任何", "普通 / 普通 10人", "英雄 / 普通 25人", "英雄 10人", "英雄 25人"],
|
||||
'modes' => array(
|
||||
[-1 => "任何", "普通 / 普通 10人", "英雄 / 普通 25人", "英雄 10人", "英雄 25人"],
|
||||
["普通", "英雄"],
|
||||
["普通 10人", "普通 25人", "英雄 10人", "英雄 25人"],
|
||||
),
|
||||
'expansions' => ["经典旧世", "燃烧的远征", "巫妖王之怒"],
|
||||
'stats' => ["力量", "敏捷", "耐力", "智力", "精神"],
|
||||
'timeAbbrev' => array(
|
||||
@@ -1208,10 +1212,6 @@ $lang = array(
|
||||
'mechanicimmune'=> '[Not affected by mechanic]:%s',
|
||||
'_extraFlags' => '[Extra Flags]:',
|
||||
'versions' => '[Difficulty Versions]:',
|
||||
'modes' => array(
|
||||
1 => ["普通", "英雄"],
|
||||
2 => ["10人普通", "25人普通", "10人英雄", "25人英雄"]
|
||||
),
|
||||
'cat' => array(
|
||||
"未分类", '野兽', '龙类', '恶魔', '元素生物', '巨人', '亡灵',
|
||||
'人型生物', '小动物', '机械', '未指定', '图腾', '非战斗宠物', '气体云雾'
|
||||
|
||||
@@ -1571,6 +1571,26 @@ CREATE TABLE `aowow_mails` (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Table structure for table `aowow_objectdifficulty`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `aowow_objectdifficulty`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `aowow_objectdifficulty` (
|
||||
`normal10` mediumint(8) unsigned NOT NULL,
|
||||
`normal25` mediumint(8) unsigned NOT NULL,
|
||||
`heroic10` mediumint(8) unsigned NOT NULL,
|
||||
`heroic25` mediumint(8) unsigned NOT NULL,
|
||||
`mapType` tinyint(3) unsigned NOT NULL,
|
||||
KEY `normal10` (`normal10`),
|
||||
KEY `normal25` (`normal25`),
|
||||
KEY `heroic10` (`heroic10`),
|
||||
KEY `heroic25` (`heroic25`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Table structure for table `aowow_objects`
|
||||
--
|
||||
@@ -2813,6 +2833,7 @@ CREATE TABLE `aowow_spelldifficulty` (
|
||||
`normal25` mediumint(8) unsigned NOT NULL,
|
||||
`heroic10` mediumint(8) unsigned NOT NULL,
|
||||
`heroic25` mediumint(8) unsigned NOT NULL,
|
||||
`mapType` tinyint(3) unsigned NOT NULL,
|
||||
KEY `normal10` (`normal10`),
|
||||
KEY `normal25` (`normal25`),
|
||||
KEY `heroic10` (`heroic10`),
|
||||
|
||||
@@ -115,6 +115,17 @@ INSERT INTO `aowow_loot_link` VALUES (19710,184465,1,0,0),(19218,184465,1,1,0),(
|
||||
/*!40000 ALTER TABLE `aowow_loot_link` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Dumping data for table `aowow_objectdifficulty`
|
||||
--
|
||||
|
||||
LOCK TABLES `aowow_objectdifficulty` WRITE;
|
||||
/*!40000 ALTER TABLE `aowow_objectdifficulty` DISABLE KEYS */;
|
||||
INSERT INTO `aowow_objectdifficulty` VALUES (181366,193426,0,0,2),(193905,193967,0,0,2),(194307,194308,194200,194201,2),(194312,194314,194313,194315,2),(194324,194328,194327,194331,2),(194789,194956,194957,194958,2),(194821,194822,0,0,2),(195046,195047,0,0,2),(195631,195632,195633,195635,2),(202178,202180,202177,202179,2),(202239,202240,202238,202241,2),(201959,202339,202338,202340,2),(0,0,195668,195672,2),(0,0,195667,195671,2),(0,0,195666,195670,2),(0,0,195665,195669,2),(185168,185169,0,0,1),(184465,184849,0,0,1),(190586,193996,0,0,1),(190663,193597,0,0,1),(191349,193603,0,0,1),(195709,195710,0,0,1),(195323,195324,0,0,1),(195374,195375,0,0,1),(201710,202336,0,0,1);
|
||||
/*!40000 ALTER TABLE `aowow_objectdifficulty` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `aowow_profiler_excludes`
|
||||
--
|
||||
|
||||
42
setup/sql/updates/1763557620_01.sql
Normal file
42
setup/sql/updates/1763557620_01.sql
Normal file
@@ -0,0 +1,42 @@
|
||||
DROP TABLE IF EXISTS `aowow_objectdifficulty`;
|
||||
CREATE TABLE `aowow_objectdifficulty` (
|
||||
`normal10` mediumint(8) unsigned NOT NULL,
|
||||
`normal25` mediumint(8) unsigned NOT NULL,
|
||||
`heroic10` mediumint(8) unsigned NOT NULL,
|
||||
`heroic25` mediumint(8) unsigned NOT NULL,
|
||||
`mapType` tinyint(3) unsigned NOT NULL,
|
||||
KEY `normal10` (`normal10`),
|
||||
KEY `normal25` (`normal25`),
|
||||
KEY `heroic10` (`heroic10`),
|
||||
KEY `heroic25` (`heroic25`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
INSERT INTO `aowow_objectdifficulty` VALUES
|
||||
(181366, 193426, 0, 0 , 2), -- naxxramas: four horsemen chest
|
||||
(193905, 193967, 0, 0 , 2), -- eoe: alexstrasza's gift
|
||||
(194307, 194308, 194200, 194201, 2), -- ulduar: cache of winter
|
||||
(194312, 194314, 194313, 194315, 2), -- ulduar: cache of storms
|
||||
(194324, 194328, 194325, 194329, 2), -- ulduar: freya's gift +1 elder
|
||||
(194324, 194328, 194326, 194330, 2), -- ulduar: freya's gift +2 elder
|
||||
(194324, 194328, 194327, 194331, 2), -- ulduar: freya's gift +3 elder
|
||||
(194789, 194956, 194957, 194958, 2), -- ulduar: cache of innovation
|
||||
(194821, 194822, 0, 0 , 2), -- ulduar: gift of the observer
|
||||
(195046, 195047, 0, 0 , 2), -- ulduar: cache of living stone
|
||||
(195631, 195632, 195633, 195635, 2), -- toc25: champions' cache
|
||||
(202178, 202180, 202177, 202179, 2), -- icc: gunship armory (horde)
|
||||
(201873, 201874, 201872, 201875, 2), -- icc: gunship armory (alliance)
|
||||
(202239, 202240, 202238, 202241, 2), -- icc: deathbringer's cache
|
||||
(201959, 202339, 202338, 202340, 2), -- icc: cache of the dreamwalker
|
||||
(0, 0, 195668, 195672, 2), -- toc25: argent crusade tribute chest 1TL
|
||||
(0, 0, 195667, 195671, 2), -- toc25: argent crusade tribute chest 25TL
|
||||
(0, 0, 195666, 195670, 2), -- toc25: argent crusade tribute chest 45TL
|
||||
(0, 0, 195665, 195669, 2), -- toc25: argent crusade tribute chest 50TL
|
||||
(185168, 185169, 0, 0 , 1), -- hellfire ramparts: reinforced fel iron chest
|
||||
(184465, 184849, 0, 0 , 1), -- mechanar: cache of the legion
|
||||
(190586, 193996, 0, 0 , 1), -- halls of stone: tribunal chest
|
||||
(190663, 193597, 0, 0 , 1), -- cot - cos: dark runed chest
|
||||
(191349, 193603, 0, 0 , 1), -- oculus: cache of eregos
|
||||
(195709, 195710, 0, 0 , 1), -- toc5: champion's cache
|
||||
(195323, 195324, 0, 0 , 1), -- toc5: confessor's cache
|
||||
(195374, 195375, 0, 0 , 1), -- toc5: eadric's cache
|
||||
(201710, 202336, 0, 0 , 1); -- hor: captain's chest
|
||||
18
setup/sql/updates/1763557620_02.sql
Normal file
18
setup/sql/updates/1763557620_02.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
ALTER TABLE `aowow_spelldifficulty`
|
||||
ADD COLUMN `mapType` tinyint(3) unsigned NOT NULL AFTER `heroic25`
|
||||
;
|
||||
|
||||
-- move linked chest for icc: gunship battle. duplicate saurfang to muradin
|
||||
DELETE FROM `aowow_loot_link` WHERE `npcId` IN (36939, 38156, 38637, 38638, 36948, 38157, 38639, 38640);
|
||||
INSERT INTO `aowow_loot_link` (`npcId`, `objectId`, `difficulty`, `priority`, `encounterId`) VALUES
|
||||
(36939, 201873, 1, 0, 847),
|
||||
(38156, 201874, 2, 0, 847),
|
||||
(38637, 201872, 3, 0, 847),
|
||||
(38638, 201875, 4, 0, 847),
|
||||
(36948, 202178, 1, 0, 847),
|
||||
(38157, 202180, 2, 0, 847),
|
||||
(38639, 202177, 3, 0, 847),
|
||||
(38640, 202179, 4, 0, 847)
|
||||
;
|
||||
|
||||
UPDATE `aowow_dbversion` SET `sql` = CONCAT(IFNULL(`sql`, ''), ' source spelldifficulty');
|
||||
@@ -26,7 +26,20 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
private array $disables = [];
|
||||
|
||||
private const /* array */ PVP_MONEY = [26045, 24581, 24579, 43589, 37836]; // Nagrand, Hellfire Pen. H, Hellfire Pen. A, Wintergrasp, Grizzly Hills
|
||||
private const /* int */ COMMON_THRESHOLD = 100;
|
||||
private const /* int */ COMMON_THRESHOLD = 30; // if an item has more than X sources it gets filtered by default in loot listviews; ancient WH versions have chance < 1% instead of checking for commonloot property.
|
||||
// but that would include the super rare vanity pet drops etc, so.. idk? Make it depend of item class and/or quality? That sounds like pain. :<
|
||||
private const /* array */ FAKE_CHESTS = array( // special cases where multiple chests share the same loot and are spawned for the same encounter
|
||||
// icc - gunship armory // if we process it like normal the contained items show up as zone drop because technically they have multiple sources. So lets avoid that!
|
||||
201873 => 202178, // A -> H
|
||||
201874 => 202180,
|
||||
201872 => 202177,
|
||||
201875 => 202179,
|
||||
// ulduar freya's gift - point +1 and +2 hardmode chests to max chest
|
||||
194329 => 194331, // 25
|
||||
194330 => 194331,
|
||||
194325 => 194327, // 10
|
||||
194326 => 194327
|
||||
);
|
||||
|
||||
public function generate(array $ids = []) : bool
|
||||
{
|
||||
@@ -41,10 +54,10 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
);
|
||||
|
||||
$this->dummyGOs = DB::Aowow()->select(
|
||||
'SELECT l1.`objectId` AS ARRAY_KEY, BIT_OR(l1.`difficulty`) AS "0", IFNULL(l2.`npcId`, l1.`npcId`) AS "1"
|
||||
FROM ?_loot_link l1
|
||||
LEFT JOIN ?_loot_link l2 ON l1.`objectId` = l2.`objectId` AND l2.`priority` = 1
|
||||
GROUP BY l1.`objectid`'
|
||||
'SELECT `normal10` AS ARRAY_KEY, 1 AS "0", `normal10` AS "1", `mapType` AS "2" FROM ?_objectdifficulty WHERE `normal10` > 0 UNION
|
||||
SELECT `normal25` AS ARRAY_KEY, 2 AS "0", `normal10` AS "1", `mapType` AS "2" FROM ?_objectdifficulty WHERE `normal25` > 0 UNION
|
||||
SELECT `heroic10` AS ARRAY_KEY, 4 AS "0", `normal10` AS "1", `mapType` AS "2" FROM ?_objectdifficulty WHERE `heroic10` > 0 UNION
|
||||
SELECT `heroic25` AS ARRAY_KEY, 8 AS "0", `normal10` AS "1", `mapType` AS "2" FROM ?_objectdifficulty WHERE `heroic25` > 0'
|
||||
);
|
||||
|
||||
$this->disables = DB::World()->selectCol(
|
||||
@@ -142,23 +155,27 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
$this->itemset(); # Meta category .. inherit from items #
|
||||
|
||||
|
||||
$t = new Timer(500);
|
||||
$d = new Timer(500);
|
||||
foreach ($this->srcBuffer as $type => $data)
|
||||
{
|
||||
$j = 0;
|
||||
$rows = [];
|
||||
$sum = count($data);
|
||||
foreach ($data as $d)
|
||||
foreach ($data as [$t, $ti, $mt, $mti, $mz, $mFlags, $modes, $sumSources])
|
||||
{
|
||||
$rows[++$j] = array_slice($d, 0, 6);
|
||||
for ($i = 1; $i < 25; $i++)
|
||||
$rows[$j][] = $d[6][$i] ?? 0;
|
||||
// can only ever be either/or .. unset if both
|
||||
if (($mFlags & (SRC_FLAG_RAID_DROP | SRC_FLAG_DUNGEON_DROP)) == (SRC_FLAG_RAID_DROP | SRC_FLAG_DUNGEON_DROP))
|
||||
$mFlags &= ~(SRC_FLAG_RAID_DROP | SRC_FLAG_DUNGEON_DROP);
|
||||
|
||||
if ($d[7] > self::COMMON_THRESHOLD)
|
||||
$rows[++$j] = [$t, $ti, $mt, $mti, $mz, $mFlags];
|
||||
for ($i = 1; $i < 25; $i++)
|
||||
$rows[$j][] = $modes[$i] ?? 0;
|
||||
|
||||
if ($sumSources > self::COMMON_THRESHOLD)
|
||||
$rows[$j][5] |= SRC_FLAG_COMMON;
|
||||
|
||||
if ($t->update())
|
||||
CLI::write('[source] - Inserting... (['.$type.'] '.$j.' / '.$sum.')', CLI::LOG_BLANK, true, true);
|
||||
if ($d->update())
|
||||
CLI::write('[source] - Inserting... (['.Type::getFileString($type).'] '.$j.' / '.$sum.')', CLI::LOG_BLANK, true, true);
|
||||
|
||||
if (!($j % 300))
|
||||
$this->insert($rows);
|
||||
@@ -196,7 +213,7 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
return true;
|
||||
}
|
||||
|
||||
private function pushBuffer(int $type, int $typeId, int $srcId, int $srcBit = 1, int $mType = 0, int $mTypeId = 0, ?int $mZoneId = null, int $mMask = 0x0, int $qty = 1) : void
|
||||
private function pushBuffer(int $type, int $typeId, int $srcId, int $srcBit = 1, int $mType = 0, int $mTypeId = 0, ?int $mZoneId = null, ?int $mMask = null, int $qty = 1) : void
|
||||
{
|
||||
if (!isset($this->srcBuffer[$type]))
|
||||
$this->srcBuffer[$type] = [];
|
||||
@@ -207,19 +224,26 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
return;
|
||||
}
|
||||
|
||||
$b = &$this->srcBuffer[$type][$typeId];
|
||||
[, , &$bType, &$bTypeId, &$bZone, &$bFlags, &$bSrc, &$bQty] = $this->srcBuffer[$type][$typeId];
|
||||
|
||||
if ($mType != $b[2] || $mTypeId != $b[3])
|
||||
$b[2] = $b[3] = null;
|
||||
if ($mType != $bType || $mTypeId != $bTypeId)
|
||||
$bType = $bTypeId = null;
|
||||
|
||||
if ($mZoneId && $b[4] === null)
|
||||
$b[4] = $mZoneId;
|
||||
else if ($mZoneId && $b[4] && $mZoneId != $b[4])
|
||||
$b[4] = 0;
|
||||
if ($bZone === null)
|
||||
$bZone = $mZoneId;
|
||||
else if ($mZoneId !== null && $mZoneId != $bZone)
|
||||
$bZone = 0;
|
||||
|
||||
$b[5] = ($b[5] ?? 0) & $mMask; // only bossdrop for now .. remove flag if regular source is available
|
||||
$b[6][$srcId] = ($b[6][$srcId] ?? 0) | $srcBit; // SIDE_X for quests, modeMask for drops, subSrc for pvp, else: 1
|
||||
$b[7] += $qty;
|
||||
if ($bFlags === null) // bossdrop, raid drop, dungeon drop .. remove flag if regular source is available
|
||||
$bFlags = $mMask;
|
||||
else if ($mMask !== null)
|
||||
{
|
||||
$bFlags &= ($mMask & SRC_FLAG_BOSSDROP);
|
||||
$bFlags |= ($mMask & (SRC_FLAG_DUNGEON_DROP | SRC_FLAG_RAID_DROP));
|
||||
}
|
||||
|
||||
$bSrc[$srcId] = ($bSrc[$srcId] ?? 0) | $srcBit; // SIDE_X for quests, modeMask for drops, subSrc for pvp, else: 1
|
||||
$bQty += $qty;
|
||||
}
|
||||
|
||||
private function insert(array &$rows) : void
|
||||
@@ -293,7 +317,7 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
CLI::write('[source] * #2 Drop [NPC]', CLI::LOG_BLANK, true, true);
|
||||
|
||||
$creatureLoot = DB::World()->select(
|
||||
'SELECT IF(clt.`Reference` > 0, -clt.`Reference`, clt.`Item`) AS "refOrItem", ct.`entry`, it.`class`, it.`subclass`, it.`spellid_1`, it.`spelltrigger_1`, it.`spellid_2`, it.`spelltrigger_2`, COUNT(1) AS "qty"
|
||||
'SELECT IF(clt.`Reference` > 0, -clt.`Reference`, clt.`Item`) AS "refOrItem", ct.`entry`, it.`class`, it.`subclass`, it.`spellid_1`, it.`spelltrigger_1`, it.`spellid_2`, it.`spelltrigger_2`, COUNT(DISTINCT clt.`Reference`) AS "qty"
|
||||
FROM creature_loot_template clt
|
||||
JOIN creature_template ct ON clt.`entry` = ct.`lootid`
|
||||
LEFT JOIN item_template it ON it.`entry` = clt.`Item` AND clt.`Reference` <= 0
|
||||
@@ -301,7 +325,8 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
GROUP BY `refOrItem`, ct.`entry`'
|
||||
);
|
||||
|
||||
$npcSpawns = DB::Aowow()->select('SELECT `typeId` AS ARRAY_KEY, IF(COUNT(DISTINCT s.`areaId`) > 1, 0, s.`areaId`) AS "areaId", z.`type` FROM ?_spawns s JOIN ?_zones z ON z.`id` = s.`areaId` WHERE s.`type` = ?d AND `typeId`IN (?a) GROUP BY `typeId`', Type::NPC, array_merge(array_column($this->dummyGOs, 1), array_filter(array_column($creatureLoot, 'entry'))));
|
||||
$linkedNpcs = DB::Aowow()->selectCol('SELECT l1.`objectId` AS ARRAY_KEY, IFNULL(l2.`npcId`, l1.`npcId`) FROM ?_loot_link l1 LEFT JOIN ?_loot_link l2 ON l1.`objectId` = l2.`objectId` AND l2.`priority` = 1 GROUP BY l1.`objectid`');
|
||||
$npcSpawns = DB::Aowow()->select('SELECT `typeId` AS ARRAY_KEY, IF(COUNT(DISTINCT s.`areaId`) > 1, 0, s.`areaId`) AS "areaId", z.`type` FROM ?_spawns s JOIN ?_zones z ON z.`id` = s.`areaId` WHERE s.`type` = ?d AND `typeId` IN (?a) GROUP BY `typeId`', Type::NPC, array_merge($linkedNpcs, array_filter(array_column($creatureLoot, 'entry'))));
|
||||
$bosses = DB::Aowow()->selectCol('SELECT `id` AS ARRAY_KEY, IF(`cuFlags` & ?d, 1, IF(`typeFlags` & 0x4 AND `rank` > 0, 1, 0)) FROM ?_creature WHERE `id` IN (?a)', NPC_CU_INSTANCE_BOSS, array_filter(array_column($creatureLoot, 'entry')));
|
||||
|
||||
foreach ($creatureLoot as $l)
|
||||
@@ -309,12 +334,12 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
$roi = $l['refOrItem'];
|
||||
$entry = $l['entry'];
|
||||
$mode = 1;
|
||||
$zoneId = 0;
|
||||
$zoneId = null;
|
||||
$mMask = 0x0;
|
||||
if (isset($this->dummyNPCs[$l['entry']]))
|
||||
[$mode, $entry] = $this->dummyNPCs[$l['entry']];
|
||||
|
||||
if (isset($bosses[$entry]) && $bosses[$entry]) // can be empty...?
|
||||
if (!empty($bosses[$entry]))
|
||||
$mMask |= SRC_FLAG_BOSSDROP;
|
||||
|
||||
if (isset($npcSpawns[$entry]))
|
||||
@@ -355,10 +380,11 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
$this->pushBuffer(Type::ITEM, $roi, SRC_DROP, $mode, $l['qty'] > 1 ? 0 : Type::NPC, $entry, $zoneId, $mMask, $l['qty']);
|
||||
}
|
||||
|
||||
|
||||
CLI::write('[source] * #2 Drop [Object]', CLI::LOG_BLANK, true, true);
|
||||
|
||||
$objectLoot = DB::World()->select(
|
||||
'SELECT IF(glt.`Reference` > 0, -glt.`Reference`, glt.`Item`) AS "refOrItem", gt.`entry`, it.`class`, it.`subclass`, it.`spellid_1`, it.`spelltrigger_1`, it.`spellid_2`, it.`spelltrigger_2`, COUNT(1) AS "qty"
|
||||
'SELECT IF(glt.`Reference` > 0, -glt.`Reference`, glt.`Item`) AS "refOrItem", gt.`entry`, it.`class`, it.`subclass`, it.`spellid_1`, it.`spelltrigger_1`, it.`spellid_2`, it.`spelltrigger_2`, COUNT(DISTINCT glt.`Reference`) AS "qty"
|
||||
FROM gameobject_loot_template glt
|
||||
JOIN gameobject_template gt ON glt.`entry` = gt.`data1`
|
||||
LEFT JOIN item_template it ON it.`entry` = glt.`Item` AND glt.`Reference` <= 0
|
||||
@@ -373,23 +399,25 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
foreach ($objectLoot as $l)
|
||||
{
|
||||
$roi = $l['refOrItem'];
|
||||
$entry = $l['entry'];
|
||||
$mode = 1 | ($this->dummyGOs[$entry][0] ?? 0);
|
||||
$zoneId = 0;
|
||||
$entry = self::FAKE_CHESTS[$l['entry']] ?? $l['entry'];
|
||||
$mode = 1;
|
||||
$zoneId = null;
|
||||
$mMask = 0x0;
|
||||
$spawn = [];
|
||||
|
||||
if (isset($this->dummyGOs[$entry])) // we know these are all boss drops
|
||||
$mMask |= SRC_FLAG_BOSSDROP;
|
||||
|
||||
if (isset($goSpawns[$entry]))
|
||||
$spawn = $goSpawns[$entry];
|
||||
else if (isset($this->dummyGOs[$entry]) && isset($npcSpawns[$this->dummyGOs[$entry][1]]))
|
||||
$spawn = $npcSpawns[$this->dummyGOs[$entry][1]];
|
||||
|
||||
if ($spawn)
|
||||
if ([$modeBit, $baseEntry, $mapType] = ($this->dummyGOs[$entry] ?? null))
|
||||
{
|
||||
switch ($spawn['type'])
|
||||
$mMask |= SRC_FLAG_BOSSDROP; // we know these are all boss drops
|
||||
$mode = $modeBit;
|
||||
$entry = $baseEntry ?: $entry;
|
||||
|
||||
if ($mapType == 1)
|
||||
$mMask |= SRC_FLAG_DUNGEON_DROP;
|
||||
if ($mapType == 2)
|
||||
$mMask |= SRC_FLAG_RAID_DROP;
|
||||
}
|
||||
else if (isset($goSpawns[$entry]))
|
||||
{
|
||||
switch ($goSpawns[$entry]['type'])
|
||||
{
|
||||
case MAP_TYPE_DUNGEON_HC:
|
||||
$mMask |= SRC_FLAG_DUNGEON_DROP; break;
|
||||
@@ -397,8 +425,16 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
case MAP_TYPE_MMODE_RAID_HC:
|
||||
$mMask |= SRC_FLAG_RAID_DROP; break;
|
||||
}
|
||||
}
|
||||
|
||||
$zoneId = $spawn['areaId'];
|
||||
if (isset($goSpawns[$entry]))
|
||||
$zoneId = $goSpawns[$entry]['areaId'];
|
||||
else if (isset($linkedNpcs[$entry]))
|
||||
{
|
||||
if (!empty($bosses[$linkedNpcs[$entry]]))
|
||||
$mMask |= SRC_FLAG_BOSSDROP;
|
||||
if (isset($npcSpawns[$linkedNpcs[$entry]]))
|
||||
$zoneId = $npcSpawns[$linkedNpcs[$entry]]['areaId'];
|
||||
}
|
||||
|
||||
if ($roi < 0 && !empty($this->refLoot[-$roi]))
|
||||
@@ -406,26 +442,27 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
foreach ($this->refLoot[-$roi] as $iId => $r)
|
||||
{
|
||||
if ($_ = $this->taughtSpell($r))
|
||||
$this->pushBuffer(Type::SPELL, $_, SRC_DROP, $mode, $l['qty'] > 1 ? 0 : Type::OBJECT, $l['entry'], $zoneId, $mMask, $l['qty']);
|
||||
$this->pushBuffer(Type::SPELL, $_, SRC_DROP, $mode, $l['qty'] > 1 ? 0 : Type::OBJECT, $entry, $zoneId, $mMask, $l['qty']);
|
||||
|
||||
$objectOT[] = $iId;
|
||||
$this->pushBuffer(Type::ITEM, $iId, SRC_DROP, $mode, $l['qty'] > 1 ? 0 : Type::OBJECT, $l['entry'], $zoneId, $mMask, $l['qty']);
|
||||
$this->pushBuffer(Type::ITEM, $iId, SRC_DROP, $mode, $l['qty'] > 1 ? 0 : Type::OBJECT, $entry, $zoneId, $mMask, $l['qty']);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($_ = $this->taughtSpell($l))
|
||||
$this->pushBuffer(Type::SPELL, $_, SRC_DROP, $mode, $l['qty'] > 1 ? 0 : Type::OBJECT, $l['entry'], $zoneId, $mMask, $l['qty']);
|
||||
$this->pushBuffer(Type::SPELL, $_, SRC_DROP, $mode, $l['qty'] > 1 ? 0 : Type::OBJECT, $entry, $zoneId, $mMask, $l['qty']);
|
||||
|
||||
$objectOT[] = $roi;
|
||||
$this->pushBuffer(Type::ITEM, $roi, SRC_DROP, $mode, $l['qty'] > 1 ? 0 : Type::OBJECT, $l['entry'], $zoneId, $mMask, $l['qty']);
|
||||
$this->pushBuffer(Type::ITEM, $roi, SRC_DROP, $mode, $l['qty'] > 1 ? 0 : Type::OBJECT, $entry, $zoneId, $mMask, $l['qty']);
|
||||
}
|
||||
|
||||
|
||||
CLI::write('[source] * #2 Drop [Item]', CLI::LOG_BLANK, true, true);
|
||||
|
||||
$itemLoot = DB::World()->select(
|
||||
'SELECT IF(ilt.`Reference` > 0, -ilt.`Reference`, ilt.`Item`) AS ARRAY_KEY, itA.`entry`, itB.`class`, itB.`subclass`, itB.`spellid_1`, itB.`spelltrigger_1`, itB.`spellid_2`, itB.`spelltrigger_2`, COUNT(1) AS "qty"
|
||||
'SELECT IF(ilt.`Reference` > 0, -ilt.`Reference`, ilt.`Item`) AS ARRAY_KEY, itA.`entry`, itB.`class`, itB.`subclass`, itB.`spellid_1`, itB.`spelltrigger_1`, itB.`spellid_2`, itB.`spelltrigger_2`, COUNT(DISTINCT ilt.`Reference`) AS "qty"
|
||||
FROM item_loot_template ilt
|
||||
JOIN item_template itA ON ilt.`entry` = itA.`entry`
|
||||
LEFT JOIN item_template itB ON itB.`entry` = ilt.`Item` AND ilt.`Reference` <= 0
|
||||
@@ -441,23 +478,25 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
foreach ($this->refLoot[-$roi] as $iId => $r)
|
||||
{
|
||||
if ($_ = $this->taughtSpell($r))
|
||||
$this->pushBuffer(Type::SPELL, $_, SRC_DROP, 1, $l['qty'] > 1 ? 0 : Type::ITEM, $l['entry'], 0, $l['qty']);
|
||||
$this->pushBuffer(Type::SPELL, $_, SRC_DROP, 1, $l['qty'] > 1 ? 0 : Type::ITEM, $l['entry'], qty: $l['qty']);
|
||||
|
||||
$itemOT[] = $iId;
|
||||
$this->pushBuffer(Type::ITEM, $iId, SRC_DROP, 1, $l['qty'] > 1 ? 0 : Type::ITEM, $l['entry'], 0, $l['qty']);
|
||||
$this->pushBuffer(Type::ITEM, $iId, SRC_DROP, 1, $l['qty'] > 1 ? 0 : Type::ITEM, $l['entry'], qty: $l['qty']);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($_ = $this->taughtSpell($l))
|
||||
$this->pushBuffer(Type::SPELL, $_, SRC_DROP, 1, $l['qty'] > 1 ? 0 : Type::ITEM, $l['entry'], 0, $l['qty']);
|
||||
$this->pushBuffer(Type::SPELL, $_, SRC_DROP, 1, $l['qty'] > 1 ? 0 : Type::ITEM, $l['entry'], qty: $l['qty']);
|
||||
|
||||
$itemOT[] = $roi;
|
||||
$this->pushBuffer(Type::ITEM, $roi, SRC_DROP, 1, $l['qty'] > 1 ? 0 : Type::ITEM, $l['entry'], 0, $l['qty']);
|
||||
$this->pushBuffer(Type::ITEM, $roi, SRC_DROP, 1, $l['qty'] > 1 ? 0 : Type::ITEM, $l['entry'], qty: $l['qty']);
|
||||
}
|
||||
|
||||
if ($itemOT)
|
||||
DB::Aowow()->query('UPDATE ?_items SET `cuFLags` = `cuFlags` | ?d WHERE `id` IN (?a)', ITEM_CU_OT_ITEMLOOT, $itemOT);
|
||||
if ($objectOT)
|
||||
DB::Aowow()->query('UPDATE ?_items SET `cuFLags` = `cuFlags` | ?d WHERE `id` IN (?a)', ITEM_CU_OT_OBJECTLOOT, $objectOT);
|
||||
}
|
||||
|
||||
@@ -537,7 +576,7 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
}
|
||||
|
||||
$mailLoot = DB::World()->select(
|
||||
'SELECT IF(mlt.`Reference` > 0, -mlt.`Reference`, mlt.`Item`) AS ARRAY_KEY, qt.`Id` AS "entry", it.`class`, it.`subclass`, it.`spellid_1`, it.`spelltrigger_1`, it.`spellid_2`, it.`spelltrigger_2`, COUNT(1) AS "qty", IF(COUNT(DISTINCT `QuestSortID`) > 1, 0, GREATEST(`QuestSortID`, 0)) AS "zone", BIT_OR(IF(qt.`AllowableRaces` & ?d AND NOT (qt.`AllowableRaces` & ?d), ?d, IF(qt.`AllowableRaces` & ?d AND NOT (qt.`AllowableRaces` & ?d), ?d, ?d))) AS "side"
|
||||
'SELECT IF(mlt.`Reference` > 0, -mlt.`Reference`, mlt.`Item`) AS ARRAY_KEY, qt.`Id` AS "entry", it.`class`, it.`subclass`, it.`spellid_1`, it.`spelltrigger_1`, it.`spellid_2`, it.`spelltrigger_2`, COUNT(DISTINCT mlt.`Reference`) AS "qty", IF(COUNT(DISTINCT `QuestSortID`) > 1, 0, GREATEST(`QuestSortID`, 0)) AS "zone", BIT_OR(IF(qt.`AllowableRaces` & ?d AND NOT (qt.`AllowableRaces` & ?d), ?d, IF(qt.`AllowableRaces` & ?d AND NOT (qt.`AllowableRaces` & ?d), ?d, ?d))) AS "side"
|
||||
FROM mail_loot_template mlt
|
||||
JOIN quest_template_addon qta ON qta.`RewardMailTemplateId` = mlt.`entry`
|
||||
JOIN quest_template qt ON qt.`ID` = qta.`ID`
|
||||
@@ -659,7 +698,7 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
CLI::write('[source] * #15 Disenchanted', CLI::LOG_BLANK, true, true);
|
||||
|
||||
$deLoot = DB::World()->select(
|
||||
'SELECT IF(dlt.`Reference` > 0, -dlt.`Reference`, dlt.`Item`) AS "refOrItem", itA.`entry`, itB.`class`, itB.`subclass`, itB.`spellid_1`, itB.`spelltrigger_1`, itB.`spellid_2`, itB.`spelltrigger_2`, COUNT(1) AS "qty"
|
||||
'SELECT IF(dlt.`Reference` > 0, -dlt.`Reference`, dlt.`Item`) AS "refOrItem", itA.`entry`, itB.`class`, itB.`subclass`, itB.`spellid_1`, itB.`spelltrigger_1`, itB.`spellid_2`, itB.`spelltrigger_2`, COUNT(DISTINCT dlt.`Reference`) AS "qty"
|
||||
FROM disenchant_loot_template dlt
|
||||
JOIN item_template itA ON dlt.`entry` = itA.`DisenchantId`
|
||||
LEFT JOIN item_template itB ON itB.`entry` = dlt.`Item` AND dlt.`Reference` <= 0
|
||||
@@ -696,7 +735,7 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
CLI::write('[source] * #16 Fished', CLI::LOG_BLANK, true, true);
|
||||
|
||||
$fishLoot = DB::World()->select(
|
||||
'SELECT src.`itemOrRef` AS "refOrItem", src.`entry`, it.`class`, it.`subclass`, it.`spellid_1`, it.`spelltrigger_1`, it.`spellid_2`, it.`spelltrigger_2`, COUNT(1) AS "qty", IF(COUNT(DISTINCT `zone`) > 2, 0, MAX(`zone`)) AS "zone"
|
||||
'SELECT src.`itemOrRef` AS "refOrItem", src.`entry`, it.`class`, it.`subclass`, it.`spellid_1`, it.`spelltrigger_1`, it.`spellid_2`, it.`spelltrigger_2`, COUNT(DISTINCT src.`itemOrRef`) AS "qty", IF(COUNT(DISTINCT `zone`) > 2, 0, MAX(`zone`)) AS "zone"
|
||||
FROM (SELECT 0 AS "entry", IF(flt.`Reference` > 0, -flt.`Reference`, flt.`Item`) AS "itemOrRef", `entry` AS "zone" FROM fishing_loot_template flt UNION
|
||||
SELECT gt.`entry`, IF(glt.`Reference` > 0, -glt.`Reference`, glt.`Item`) AS "itemOrRef", 0 AS "zone" FROM gameobject_template gt JOIN gameobject_loot_template glt ON glt.`entry` = gt.`data1` WHERE `type` = ?d AND gt.`data1` > 0) src
|
||||
LEFT JOIN item_template it ON src.`itemOrRef` > 0 AND src.`itemOrRef` = it.`entry`
|
||||
@@ -717,7 +756,7 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
{
|
||||
$roi = $l['refOrItem'];
|
||||
$l['zone'] = $areaParent[$l['zone']] ?? $l['zone'];
|
||||
$zoneId = $goSpawns[$l['entry']] ?? 0;
|
||||
$zoneId = $goSpawns[$l['entry']] ?? null;
|
||||
if ($l['zone'] != $zoneId)
|
||||
$zoneId = 0;
|
||||
|
||||
@@ -746,7 +785,7 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
CLI::write('[source] * #17 Gathered', CLI::LOG_BLANK, true, true);
|
||||
|
||||
$herbLoot = DB::World()->select(
|
||||
'SELECT src.`itemOrRef` AS "refOrItem", src.`entry`, it.`class`, it.`subclass`, it.`spellid_1`, it.`spelltrigger_1`, it.`spellid_2`, it.`spelltrigger_2`, COUNT(1) AS "qty", src.`srcType`
|
||||
'SELECT src.`itemOrRef` AS "refOrItem", src.`entry`, it.`class`, it.`subclass`, it.`spellid_1`, it.`spelltrigger_1`, it.`spellid_2`, it.`spelltrigger_2`, COUNT(DISTINCT src.`itemOrRef`) AS "qty", src.`srcType`
|
||||
FROM (SELECT ct.`entry`, IF(slt.`Reference` > 0, -slt.`Reference`, slt.`Item`) `itemOrRef`, ?d AS "srcType" FROM creature_template ct JOIN skinning_loot_template slt ON slt.`entry` = ct.`skinloot` WHERE (`type_flags` & ?d) AND ct.`skinloot` > 0 UNION
|
||||
SELECT gt.`entry`, IF(glt.`Reference` > 0, -glt.`Reference`, glt.`Item`) `itemOrRef`, ?d AS "srcType" FROM gameobject_template gt JOIN gameobject_loot_template glt ON glt.`entry` = gt.`data1` WHERE gt.`type` = ?d AND gt.`data1` > 0 AND gt.`data0` IN (?a)) src
|
||||
LEFT JOIN item_template it ON src.itemOrRef > 0 AND src.`itemOrRef` = it.`entry`
|
||||
@@ -761,8 +800,8 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
return;
|
||||
}
|
||||
|
||||
$spawns[Type::OBJECT] = DB::Aowow()->selectCol('SELECT `typeId` AS ARRAY_KEY, IF(COUNT(DISTINCT `areaId`) > 1, 0, `areaId`) FROM ?_spawns WHERE `type` = ?d AND `typeId` IN (?a) GROUP BY `typeId`', Type::OBJECT, array_column(array_filter($herbLoot, function($x) { return $x['srcType'] == Type::OBJECT; }), 'entry'));
|
||||
$spawns[Type::NPC] = DB::Aowow()->selectCol('SELECT `typeId` AS ARRAY_KEY, IF(COUNT(DISTINCT `areaId`) > 1, 0, `areaId`) FROM ?_spawns WHERE `type` = ?d AND `typeId` IN (?a) GROUP BY `typeId`', Type::NPC, array_column(array_filter($herbLoot, function($x) { return $x['srcType'] == Type::NPC; }), 'entry'));
|
||||
$spawns[Type::OBJECT] = DB::Aowow()->selectCol('SELECT `typeId` AS ARRAY_KEY, IF(COUNT(DISTINCT `areaId`) > 1, 0, `areaId`) FROM ?_spawns WHERE `type` = ?d AND `typeId` IN (?a) GROUP BY `typeId`', Type::OBJECT, array_column(array_filter($herbLoot, fn($x) => $x['srcType'] == Type::OBJECT), 'entry'));
|
||||
$spawns[Type::NPC] = DB::Aowow()->selectCol('SELECT `typeId` AS ARRAY_KEY, IF(COUNT(DISTINCT `areaId`) > 1, 0, `areaId`) FROM ?_spawns WHERE `type` = ?d AND `typeId` IN (?a) GROUP BY `typeId`', Type::NPC, array_column(array_filter($herbLoot, fn($x) => $x['srcType'] == Type::NPC), 'entry'));
|
||||
|
||||
foreach ($herbLoot as $l)
|
||||
{
|
||||
@@ -772,7 +811,7 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
if (isset($this->dummyNPCs[$l['entry']]) && $l['srcType'] == Type::NPC)
|
||||
[$mode, $entry] = $this->dummyNPCs[$l['entry']];
|
||||
|
||||
$zoneId = $spawns[$l['srcType']][$l['entry']] ?? 0;
|
||||
$zoneId = $spawns[$l['srcType']][$l['entry']] ?? null;
|
||||
|
||||
if ($roi < 0 && !empty($this->refLoot[-$roi]))
|
||||
{
|
||||
@@ -799,7 +838,7 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
CLI::write('[source] * #18 Milled', CLI::LOG_BLANK, true, true);
|
||||
|
||||
$millLoot = DB::World()->select(
|
||||
'SELECT IF(mlt.`Reference` > 0, -mlt.`Reference`, mlt.`Item`) AS "refOrItem", itA.`entry`, itB.`class`, itB.`subclass`, itB.`spellid_1`, itB.`spelltrigger_1`, itB.`spellid_2`, itB.`spelltrigger_2`, COUNT(1) AS "qty"
|
||||
'SELECT IF(mlt.`Reference` > 0, -mlt.`Reference`, mlt.`Item`) AS "refOrItem", itA.`entry`, itB.`class`, itB.`subclass`, itB.`spellid_1`, itB.`spelltrigger_1`, itB.`spellid_2`, itB.`spelltrigger_2`, COUNT(DISTINCT mlt.`Reference`) AS "qty"
|
||||
FROM milling_loot_template mlt
|
||||
JOIN item_template itA ON mlt.`entry` = itA.`entry`
|
||||
LEFT JOIN item_template itB ON itB.`entry` = mlt.`Item` AND mlt.`Reference` <= 0
|
||||
@@ -835,7 +874,7 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
CLI::write('[source] * #19 Mined', CLI::LOG_BLANK, true, true);
|
||||
|
||||
$mineLoot = DB::World()->select(
|
||||
'SELECT src.`itemOrRef` AS "refOrItem", src.`entry`, it.`class`, it.`subclass`, it.`spellid_1`, it.`spelltrigger_1`, it.`spellid_2`, it.`spelltrigger_2`, COUNT(1) AS "qty", src.`srcType`
|
||||
'SELECT src.`itemOrRef` AS "refOrItem", src.`entry`, it.`class`, it.`subclass`, it.`spellid_1`, it.`spelltrigger_1`, it.`spellid_2`, it.`spelltrigger_2`, COUNT(DISTINCT src.`itemOrRef`) AS "qty", src.`srcType`
|
||||
FROM (SELECT ct.`entry`, IF(slt.`Reference` > 0, -slt.`Reference`, slt.`Item`) `itemOrRef`, ?d AS "srcType" FROM creature_template ct JOIN skinning_loot_template slt ON slt.`entry` = ct.`skinloot` WHERE (`type_flags` & ?d) AND ct.`skinloot` > 0 UNION
|
||||
SELECT gt.`entry`, IF(glt.`Reference` > 0, -glt.`Reference`, glt.`Item`) `itemOrRef`, ?d AS "srcType" FROM gameobject_template gt JOIN gameobject_loot_template glt ON glt.`entry` = gt.`data1` WHERE gt.`type` = ?d AND gt.`data1` > 0 AND gt.`data0` IN (?a)) src
|
||||
LEFT JOIN item_template it ON src.itemOrRef > 0 AND src.`itemOrRef` = it.`entry`
|
||||
@@ -850,8 +889,8 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
return;
|
||||
}
|
||||
|
||||
$spawns[Type::OBJECT] = DB::Aowow()->selectCol('SELECT `typeId` AS ARRAY_KEY, IF(COUNT(DISTINCT `areaId`) > 1, 0, `areaId`) FROM ?_spawns WHERE `type` = ?d AND `typeId`IN (?a) GROUP BY `typeId`', Type::OBJECT, array_column(array_filter($mineLoot, function($x) { return $x['srcType'] == Type::OBJECT; }), 'entry'));
|
||||
$spawns[Type::NPC] = DB::Aowow()->selectCol('SELECT `typeId` AS ARRAY_KEY, IF(COUNT(DISTINCT `areaId`) > 1, 0, `areaId`) FROM ?_spawns WHERE `type` = ?d AND `typeId`IN (?a) GROUP BY `typeId`', Type::NPC, array_column(array_filter($mineLoot, function($x) { return $x['srcType'] == Type::NPC; }), 'entry'));
|
||||
$spawns[Type::OBJECT] = DB::Aowow()->selectCol('SELECT `typeId` AS ARRAY_KEY, IF(COUNT(DISTINCT `areaId`) > 1, 0, `areaId`) FROM ?_spawns WHERE `type` = ?d AND `typeId`IN (?a) GROUP BY `typeId`', Type::OBJECT, array_column(array_filter($mineLoot, fn($x) => $x['srcType'] == Type::OBJECT), 'entry'));
|
||||
$spawns[Type::NPC] = DB::Aowow()->selectCol('SELECT `typeId` AS ARRAY_KEY, IF(COUNT(DISTINCT `areaId`) > 1, 0, `areaId`) FROM ?_spawns WHERE `type` = ?d AND `typeId`IN (?a) GROUP BY `typeId`', Type::NPC, array_column(array_filter($mineLoot, fn($x) => $x['srcType'] == Type::NPC), 'entry'));
|
||||
|
||||
foreach ($mineLoot as $l)
|
||||
{
|
||||
@@ -861,7 +900,7 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
if (isset($this->dummyNPCs[$l['entry']]) && $l['srcType'] == Type::NPC)
|
||||
[$mode, $entry] = $this->dummyNPCs[$l['entry']];
|
||||
|
||||
$zoneId = $spawns[$l['srcType']][$l['entry']] ?? 0;
|
||||
$zoneId = $spawns[$l['srcType']][$l['entry']] ?? null;
|
||||
|
||||
if ($roi < 0 && !empty($this->refLoot[-$roi]))
|
||||
{
|
||||
@@ -888,7 +927,7 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
CLI::write('[source] * #20 Prospected', CLI::LOG_BLANK, true, true);
|
||||
|
||||
$prospectLoot = DB::World()->select(
|
||||
'SELECT IF(plt.`Reference` > 0, -plt.`Reference`, plt.`Item`) AS "refOrItem", itA.`entry`, itB.`class`, itB.`subclass`, itB.`spellid_1`, itB.`spelltrigger_1`, itB.`spellid_2`, itB.`spelltrigger_2`, COUNT(1) AS "qty"
|
||||
'SELECT IF(plt.`Reference` > 0, -plt.`Reference`, plt.`Item`) AS "refOrItem", itA.`entry`, itB.`class`, itB.`subclass`, itB.`spellid_1`, itB.`spelltrigger_1`, itB.`spellid_2`, itB.`spelltrigger_2`, COUNT(DISTINCT plt.`Reference`) AS "qty"
|
||||
FROM prospecting_loot_template plt
|
||||
JOIN item_template itA ON plt.`entry` = itA.`entry`
|
||||
LEFT JOIN item_template itB ON itB.`entry` = plt.`Item` AND plt.`Reference` <= 0
|
||||
@@ -924,7 +963,7 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
CLI::write('[source] * #21 Pickpocket', CLI::LOG_BLANK, true, true);
|
||||
|
||||
$theftLoot = DB::World()->select(
|
||||
'SELECT src.`itemOrRef` AS "refOrItem", src.`entry`, it.`class`, it.`subclass`, it.`spellid_1`, it.`spelltrigger_1`, it.`spellid_2`, it.`spelltrigger_2`, COUNT(1) AS "qty"
|
||||
'SELECT src.`itemOrRef` AS "refOrItem", src.`entry`, it.`class`, it.`subclass`, it.`spellid_1`, it.`spelltrigger_1`, it.`spellid_2`, it.`spelltrigger_2`, COUNT(DISTINCT src.`itemOrRef`) AS "qty"
|
||||
FROM (SELECT ct.`entry`, IF(plt.`Reference` > 0, -plt.`Reference`, plt.`Item`) `itemOrRef` FROM creature_template ct JOIN pickpocketing_loot_template plt ON plt.`entry` = ct.`pickpocketloot` WHERE ct.`pickpocketloot` > 0) src
|
||||
LEFT JOIN item_template it ON src.`itemOrRef` > 0 AND src.`itemOrRef` = it.`entry`
|
||||
GROUP BY `refOrItem`, src.`entry`'
|
||||
@@ -946,7 +985,7 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
if (isset($this->dummyNPCs[$l['entry']]))
|
||||
[$mode, $entry] = $this->dummyNPCs[$l['entry']];
|
||||
|
||||
$zoneId = $spawns[$l['entry']] ?? 0;
|
||||
$zoneId = $spawns[$l['entry']] ?? null;
|
||||
|
||||
if ($roi < 0 && !empty($this->refLoot[-$roi]))
|
||||
{
|
||||
@@ -973,7 +1012,7 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
CLI::write('[source] * #22 Salvaged', CLI::LOG_BLANK, true, true);
|
||||
|
||||
$salvageLoot = DB::World()->select(
|
||||
'SELECT src.`itemOrRef` AS "refOrItem", src.`entry`, it.`class`, it.`subclass`, it.`spellid_1`, it.`spelltrigger_1`, it.`spellid_2`, it.`spelltrigger_2`, COUNT(1) AS "qty"
|
||||
'SELECT src.`itemOrRef` AS "refOrItem", src.`entry`, it.`class`, it.`subclass`, it.`spellid_1`, it.`spelltrigger_1`, it.`spellid_2`, it.`spelltrigger_2`, COUNT(DISTINCT src.`itemOrRef`) AS "qty"
|
||||
FROM (SELECT ct.`entry`, IF(slt.`Reference` > 0, -slt.`Reference`, slt.`Item`) `itemOrRef` FROM creature_template ct JOIN skinning_loot_template slt ON slt.`entry` = ct.`skinloot` WHERE (`type_flags` & ?d) AND ct.`skinloot` > 0) src
|
||||
LEFT JOIN item_template it ON src.`itemOrRef` > 0 AND src.`itemOrRef` = it.`entry`
|
||||
GROUP BY `refOrItem`, src.`entry`',
|
||||
@@ -996,7 +1035,7 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
if (isset($this->dummyNPCs[$l['entry']]))
|
||||
[$mode, $entry] = $this->dummyNPCs[$l['entry']];
|
||||
|
||||
$zoneId = $spawns[$l['entry']] ?? 0;
|
||||
$zoneId = $spawns[$l['entry']] ?? null;
|
||||
|
||||
if ($roi < 0 && !empty($this->refLoot[-$roi]))
|
||||
{
|
||||
@@ -1023,7 +1062,7 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
CLI::write('[source] * #23 Skinned', CLI::LOG_BLANK, true, true);
|
||||
|
||||
$skinLoot = DB::World()->select(
|
||||
'SELECT src.`itemOrRef` AS "refOrItem", src.`entry`, it.`class`, it.`subclass`, it.`spellid_1`, it.`spelltrigger_1`, it.`spellid_2`, it.`spelltrigger_2`, COUNT(1) AS "qty"
|
||||
'SELECT src.`itemOrRef` AS "refOrItem", src.`entry`, it.`class`, it.`subclass`, it.`spellid_1`, it.`spelltrigger_1`, it.`spellid_2`, it.`spelltrigger_2`, COUNT(DISTINCT src.`itemOrRef`) AS "qty"
|
||||
FROM (SELECT ct.`entry`, IF(slt.`Reference` > 0, -slt.`Reference`, slt.`Item`) `itemOrRef` FROM creature_template ct JOIN skinning_loot_template slt ON slt.`entry` = ct.`skinloot` WHERE (`type_flags` & ?d) = 0 AND ct.`skinloot` > 0 AND ct.`type` <> 13) src
|
||||
LEFT JOIN item_template it ON src.`itemOrRef` > 0 AND src.`itemOrRef` = it.`entry`
|
||||
GROUP BY `refOrItem`, src.`entry`',
|
||||
@@ -1046,7 +1085,7 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
if (isset($this->dummyNPCs[$l['entry']]))
|
||||
[$mode, $entry] = $this->dummyNPCs[$l['entry']];
|
||||
|
||||
$zoneId = $spawns[$l['entry']] ?? 0;
|
||||
$zoneId = $spawns[$l['entry']] ?? null;
|
||||
|
||||
if ($roi < 0 && !empty($this->refLoot[-$roi]))
|
||||
{
|
||||
|
||||
@@ -17,17 +17,64 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
|
||||
protected $dbcSourceFiles = ['spelldifficulty'];
|
||||
protected $worldDependency = ['spelldifficulty_dbc'];
|
||||
protected $setupAfter = [['creature', 'spawns'], []];
|
||||
|
||||
public function generate(array $ids = []) : bool
|
||||
{
|
||||
DB::Aowow()->query('TRUNCATE TABLE ?_spelldifficulty');
|
||||
|
||||
DB::Aowow()->query('INSERT INTO ?_spelldifficulty SELECT GREATEST(`normal10`, 0), GREATEST(`normal25`, 0), GREATEST(`heroic10`, 0), GREATEST(`heroic25`, 0) FROM dbc_spelldifficulty');
|
||||
DB::Aowow()->query('INSERT INTO ?_spelldifficulty SELECT GREATEST(`normal10`, 0), GREATEST(`normal25`, 0), GREATEST(`heroic10`, 0), GREATEST(`heroic25`, 0), IF(`heroic10` > 0, 2, 0) FROM dbc_spelldifficulty');
|
||||
|
||||
$rows = DB::World()->select('SELECT `spellid0`, `spellid1`, `spellid2`, `spellid3` FROM spelldifficulty_dbc');
|
||||
$rows = DB::World()->select('SELECT `spellid0`, `spellid1`, `spellid2`, `spellid3`, IF(`spellid2` > 0, 2, 0) FROM spelldifficulty_dbc');
|
||||
foreach ($rows as $r)
|
||||
DB::Aowow()->query('INSERT INTO ?_spelldifficulty VALUES (?a)', array_values($r));
|
||||
|
||||
|
||||
CLI::write('[spelldifficulty] - trying to assign map type by traversing creature spells > spawns');
|
||||
|
||||
// try to update mode of ambiguous entries
|
||||
$baseSpells = DB::Aowow()->selectCol('SELECT `normal10` FROM ?_spelldifficulty WHERE `heroic10` = 0 AND `heroic25` = 0');
|
||||
|
||||
for ($i = 1; $i < 9; $i++)
|
||||
DB::Aowow()->query(
|
||||
'UPDATE ?_spelldifficulty sd,
|
||||
(SELECT c.?# AS "spell", BIT_OR(CASE WHEN z.`type` = ?d THEN 1 WHEN z.`type` = ?d THEN 2 WHEN z.`type` = ?d THEN 2 ELSE 0 END) AS "mapType"
|
||||
FROM ?_creature c
|
||||
JOIN ?_spawns s ON c.id = s.typeId AND s.type = ?d
|
||||
JOIN ?_zones z ON z.id = s.areaId
|
||||
WHERE c.?# IN (?a)
|
||||
GROUP BY c.?#
|
||||
HAVING c.?# <> 0) x
|
||||
SET sd.`mapType` = x.`mapType`
|
||||
WHERE sd.`normal10` = x.`spell`',
|
||||
'spell'.$i, MAP_TYPE_DUNGEON_HC, MAP_TYPE_MMODE_RAID, MAP_TYPE_MMODE_RAID_HC,
|
||||
Type::NPC, 'spell'.$i, $baseSpells, 'spell'.$i, 'spell'.$i
|
||||
);
|
||||
|
||||
|
||||
CLI::write('[spelldifficulty] - trying to assign map type by traversing smart_scripts > spawns');
|
||||
|
||||
$smartCaster = [];
|
||||
foreach ($baseSpells as $bs)
|
||||
if ($owner = SmartAI::getOwnerOfSpellCast($bs))
|
||||
foreach ($owner as $type => $caster)
|
||||
$smartCaster[$type][$bs] = $caster;
|
||||
|
||||
foreach ($smartCaster as $type => $spells)
|
||||
foreach ($spells as $spellId => $casterEntries)
|
||||
DB::Aowow()->query(
|
||||
'UPDATE ?_spelldifficulty sd,
|
||||
(SELECT BIT_OR(CASE WHEN z.`type` = ?d THEN 1 WHEN z.`type` = ?d THEN 2 WHEN z.`type` = ?d THEN 2 ELSE 0 END) AS "mapType"
|
||||
FROM ?_spawns s
|
||||
JOIN ?_zones z ON z.id = s.areaId
|
||||
WHERE s.type = ?d AND s.typeId IN (?a) ) sp
|
||||
SET sd.`mapType` = IF(sp.`mapType` > 2, 0, sp.`mapType`)
|
||||
WHERE sd.`normal10` = ?d',
|
||||
MAP_TYPE_DUNGEON_HC, MAP_TYPE_MMODE_RAID, MAP_TYPE_MMODE_RAID_HC,
|
||||
$type, $casterEntries,
|
||||
$spellId
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1651,7 +1651,7 @@ span.icon-instance8 {
|
||||
|
||||
.listview-note {
|
||||
line-height: 16px;
|
||||
clear: left;
|
||||
/* clear: left; */
|
||||
}
|
||||
|
||||
.listview table {
|
||||
|
||||
Reference in New Issue
Block a user