[['ft', 'qse', 'dct1', 'dct2', 'dct3'], 's' => ', IFNULL(dct1.id, IFNULL(dct2.id, IFNULL(dct3.id, 0))) AS parentId, IFNULL(dct1.name_loc0, IFNULL(dct2.name_loc0, IFNULL(dct3.name_loc0, ""))) AS parent_loc0, IFNULL(dct1.name_loc2, IFNULL(dct2.name_loc2, IFNULL(dct3.name_loc2, ""))) AS parent_loc2, IFNULL(dct1.name_loc3, IFNULL(dct2.name_loc3, IFNULL(dct3.name_loc3, ""))) AS parent_loc3, IFNULL(dct1.name_loc4, IFNULL(dct2.name_loc4, IFNULL(dct3.name_loc4, ""))) AS parent_loc4, IFNULL(dct1.name_loc6, IFNULL(dct2.name_loc6, IFNULL(dct3.name_loc6, ""))) AS parent_loc6, IFNULL(dct1.name_loc8, IFNULL(dct2.name_loc8, IFNULL(dct3.name_loc8, ""))) AS parent_loc8, IF(dct1.difficultyEntry1 = ct.id, 1, IF(dct2.difficultyEntry2 = ct.id, 2, IF(dct3.difficultyEntry3 = ct.id, 3, 0))) AS difficultyMode'], 'dct1' => ['j' => ['?_creature dct1 ON ct.cuFlags & 0x02 AND dct1.difficultyEntry1 = ct.id', true]], 'dct2' => ['j' => ['?_creature dct2 ON ct.cuFlags & 0x02 AND dct2.difficultyEntry2 = ct.id', true]], 'dct3' => ['j' => ['?_creature dct3 ON ct.cuFlags & 0x02 AND dct3.difficultyEntry3 = ct.id', true]], 'ft' => ['j' => '?_factiontemplate ft ON ft.id = ct.faction', 's' => ', ft.A, ft.H, ft.factionId'], 'qse' => ['j' => ['?_quests_startend qse ON qse.type = 1 AND qse.typeId = ct.id', true], 's' => ', IF(min(qse.method) = 1 OR max(qse.method) = 3, 1, 0) AS startsQuests, IF(min(qse.method) = 2 OR max(qse.method) = 3, 1, 0) AS endsQuests', 'g' => 'ct.id'], 'qt' => ['j' => '?_quests qt ON qse.questId = qt.id'], 's' => ['j' => ['?_spawns s ON s.type = 1 AND s.typeId = ct.id', true]] ); public function __construct($conditions = [], $miscData = null) { parent::__construct($conditions, $miscData); if ($this->error) return; // post processing foreach ($this->iterate() as $_id => &$curTpl) { // check for attackspeeds if (!$curTpl['atkSpeed']) $curTpl['atkSpeed'] = 2.0; else $curTpl['atkSpeed'] /= 1000; if (!$curTpl['rngAtkSpeed']) $curTpl['rngAtkSpeed'] = 2.0; else $curTpl['rngAtkSpeed'] /= 1000; } } public static function getName($id) { $n = DB::Aowow()->SelectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8 FROM ?_creature WHERE id = ?d', $id); return Util::localizedString($n, 'name'); } public function renderTooltip() { if (!$this->curTpl) return null; $level = '??'; $type = $this->curTpl['type']; $row3 = [Lang::game('level')]; $fam = $this->curTpl['family']; if (!($this->curTpl['typeFlags'] & 0x4)) { $level = $this->curTpl['minLevel']; if ($level != $this->curTpl['maxLevel']) $level .= ' - '.$this->curTpl['maxLevel']; } else $level = '??'; $row3[] = $level; if ($type) $row3[] = Lang::game('ct', $type); if ($_ = Lang::npc('rank', $this->curTpl['rank'])) $row3[] = '('.$_.')'; $x = ''; $x .= ''; if ($sn = $this->getField('subname', true)) $x .= ''; $x .= ''; if ($type == 1 && $fam) // 1: Beast $x .= ''; $fac = new FactionList(array([['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0], ['id', (int)$this->getField('factionId')])); if (!$fac->error) $x .= ''; $x .= '
'.Util::htmlEscape($this->getField('name', true)).'
'.Util::htmlEscape($sn).'
'.implode(' ', $row3).'
'.Lang::game('fa', $fam).'
'.$fac->getField('name', true).'
'; return $x; } public function getRandomModelId() { // dwarf?? [null, 30754, 30753, 30755, 30736] // totems use hardcoded models, tauren model is base $totems = [null, 4589, 4588, 4587, 4590]; // slot => modelId $data = []; for ($i = 1; $i < 5; $i++) if ($_ = $this->curTpl['displayId'.$i]) $data[] = $_; if (count($data) == 1 && ($slotId = array_search($data[0], $totems))) $data = DB::World()->selectCol('SELECT DisplayId FROM player_totem_model WHERE TotemSlot = ?d', $slotId); return !$data ? 0 : $data[array_rand($data)]; } public function getBaseStats(string $type) : array { // i'm aware of the BaseVariance/RangedVariance fields ... i'm just totaly unsure about the whole damage calculation switch ($type) { case 'health': $hMin = $this->getField('healthMin'); $hMax = $this->getField('healthMax'); return [$hMin, $hMax]; case 'power': $mMin = $this->getField('manaMin'); $mMax = $this->getField('manaMax'); return [$mMin, $mMax]; case 'armor': $aMin = $this->getField('armorMin'); $aMax = $this->getField('armorMax'); return [$aMin, $aMax]; case 'melee': $mleMin = ($this->getField('dmgMin') + ($this->getField('mleAtkPwrMin') / 14)) * $this->getField('dmgMultiplier') * $this->getField('atkSpeed'); $mleMax = ($this->getField('dmgMax') * 1.5 + ($this->getField('mleAtkPwrMax') / 14)) * $this->getField('dmgMultiplier') * $this->getField('atkSpeed'); return [$mleMin, $mleMax]; case 'ranged': $rngMin = ($this->getField('dmgMin') + ($this->getField('rngAtkPwrMin') / 14)) * $this->getField('dmgMultiplier') * $this->getField('rngAtkSpeed'); $rngMax = ($this->getField('dmgMax') * 1.5 + ($this->getField('rngAtkPwrMax') / 14)) * $this->getField('dmgMultiplier') * $this->getField('rngAtkSpeed'); return [$rngMin, $rngMax]; case 'resistance': $r = []; for ($i = SPELL_SCHOOL_HOLY; $i < SPELL_SCHOOL_ARCANE+1; $i++) $r[$i] = $this->getField('resistance'.$i); return $r; default: return []; } } public function isBoss() { return ($this->curTpl['cuFlags'] & NPC_CU_INSTANCE_BOSS) || ($this->curTpl['typeFlags'] & 0x4 && $this->curTpl['rank']); } public function getListviewData($addInfoMask = 0x0) { /* looks like this data differs per occasion * * NPCINFO_TAMEABLE (0x1): include texture & react * NPCINFO_MODEL (0x2): * NPCINFO_REP (0x4): include repreward */ $data = []; $rewRep = []; if ($addInfoMask & NPCINFO_REP && $this->getFoundIDs()) { $rewRep = DB::World()->selectCol(' SELECT creature_id AS ARRAY_KEY, RewOnKillRepFaction1 AS ARRAY_KEY2, RewOnKillRepValue1 FROM creature_onkill_reputation WHERE creature_id IN (?a) AND RewOnKillRepFaction1 > 0 UNION SELECT creature_id AS ARRAY_KEY, RewOnKillRepFaction2 AS ARRAY_KEY2, RewOnKillRepValue2 FROM creature_onkill_reputation WHERE creature_id IN (?a) AND RewOnKillRepFaction2 > 0', $this->getFoundIDs(), $this->getFoundIDs() ); } foreach ($this->iterate() as $__) { if ($addInfoMask & NPCINFO_MODEL) { $texStr = strtolower($this->curTpl['textureString']); if (isset($data[$texStr])) { if ($data[$texStr]['minLevel'] > $this->curTpl['minLevel']) $data[$texStr]['minLevel'] = $this->curTpl['minLevel']; if ($data[$texStr]['maxLevel'] < $this->curTpl['maxLevel']) $data[$texStr]['maxLevel'] = $this->curTpl['maxLevel']; $data[$texStr]['count']++; } else $data[$texStr] = array( 'family' => $this->curTpl['family'], 'minLevel' => $this->curTpl['minLevel'], 'maxLevel' => $this->curTpl['maxLevel'], 'modelId' => $this->curTpl['modelId'], 'displayId' => $this->curTpl['displayId1'], 'skin' => $texStr, 'count' => 1 ); } else { $data[$this->id] = array( 'family' => $this->curTpl['family'], 'minlevel' => $this->curTpl['minLevel'], 'maxlevel' => $this->curTpl['maxLevel'], 'id' => $this->id, 'boss' => $this->isBoss() ? 1 : 0, 'classification' => $this->curTpl['rank'], 'location' => $this->getSpawns(SPAWNINFO_ZONES), 'name' => $this->getField('name', true), 'type' => $this->curTpl['type'], 'react' => [$this->curTpl['A'], $this->curTpl['H']], ); if ($this->getField('startsQuests')) $data[$this->id]['hasQuests'] = 1; if ($_ = $this->getField('subname', true)) $data[$this->id]['tag'] = $_; if ($addInfoMask & NPCINFO_TAMEABLE) // only first skin of first model ... we're omitting potentially 11 skins here .. but the lv accepts only one .. w/e $data[$this->id]['skin'] = $this->curTpl['textureString']; if ($addInfoMask & NPCINFO_REP) { $data[$this->id]['reprewards'] = []; if ($rewRep[$this->id]) foreach ($rewRep[$this->id] as $fac => $val) $data[$this->id]['reprewards'][] = [$fac, $val]; } } } ksort($data); return $data; } public function getJSGlobals($addMask = 0) { $data = []; foreach ($this->iterate() as $__) $data[Type::NPC][$this->id] = ['name' => $this->getField('name', true)]; return $data; } public function getSourceData(int $id = 0) : array { $data = []; foreach ($this->iterate() as $__) { if ($id && $id != $this->id) continue; $data[$this->id] = array( 'n' => $this->getField('parentId') ? $this->getField('parent', true) : $this->getField('name', true), 't' => Type::NPC, 'ti' => $this->getField('parentId') ?: $this->id ); } return $data; } } class CreatureListFilter extends Filter { public $extraOpts = null; protected $enums = array( 3 => parent::ENUM_FACTION, // faction 6 => parent::ENUM_ZONE, // foundin 42 => parent::ENUM_FACTION, // increasesrepwith 43 => parent::ENUM_FACTION, // decreasesrepwith 38 => parent::ENUM_EVENT // relatedevent ); protected $genericFilter = array( 1 => [FILTER_CR_CALLBACK, 'cbHealthMana', 'healthMax', 'healthMin'], // health [num] 2 => [FILTER_CR_CALLBACK, 'cbHealthMana', 'manaMin', 'manaMax' ], // mana [num] 3 => [FILTER_CR_CALLBACK, 'cbFaction', null, null ], // faction [enum] 5 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_REPAIRER ], // canrepair 6 => [FILTER_CR_ENUM, 's.areaId', false, true ], // foundin 7 => [FILTER_CR_CALLBACK, 'cbQuestRelation', 'startsQuests', 0x1 ], // startsquest [enum] 8 => [FILTER_CR_CALLBACK, 'cbQuestRelation', 'endsQuests', 0x2 ], // endsquest [enum] 9 => [FILTER_CR_BOOLEAN, 'lootId', ], // lootable 10 => [FILTER_CR_CALLBACK, 'cbRegularSkinLoot', NPC_TYPEFLAG_SPECIALLOOT ], // skinnable [yn] 11 => [FILTER_CR_BOOLEAN, 'pickpocketLootId', ], // pickpocketable 12 => [FILTER_CR_CALLBACK, 'cbMoneyDrop', null, null ], // averagemoneydropped [op] [int] 15 => [FILTER_CR_CALLBACK, 'cbSpecialSkinLoot', NPC_TYPEFLAG_HERBLOOT, null ], // gatherable [yn] 16 => [FILTER_CR_CALLBACK, 'cbSpecialSkinLoot', NPC_TYPEFLAG_MININGLOOT, null ], // minable [yn] 18 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_AUCTIONEER ], // auctioneer 19 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_BANKER ], // banker 20 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_BATTLEMASTER ], // battlemaster 21 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_FLIGHT_MASTER ], // flightmaster 22 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_GUILD_MASTER ], // guildmaster 23 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_INNKEEPER ], // innkeeper 24 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_CLASS_TRAINER ], // talentunlearner 25 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_GUILD_MASTER ], // tabardvendor 27 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_STABLE_MASTER ], // stablemaster 28 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_TRAINER ], // trainer 29 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_VENDOR ], // vendor 31 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots 32 => [FILTER_CR_FLAG, 'cuFlags', NPC_CU_INSTANCE_BOSS ], // instanceboss 33 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments 34 => [FILTER_CR_STRING, 'modelId', STR_MATCH_EXACT | STR_ALLOW_SHORT ], // usemodel [str] (wants int in string fmt <_<) 35 => [FILTER_CR_STRING, 'textureString' ], // useskin [str] 37 => [FILTER_CR_NUMERIC, 'id', NUM_CAST_INT, true ], // id 38 => [FILTER_CR_CALLBACK, 'cbRelEvent', null, null ], // relatedevent [enum] 40 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos 41 => [FILTER_CR_NYI_PH, 1, null ], // haslocation [yn] [staff] 42 => [FILTER_CR_CALLBACK, 'cbReputation', '>', null ], // increasesrepwith [enum] 43 => [FILTER_CR_CALLBACK, 'cbReputation', '<', null ], // decreasesrepwith [enum] 44 => [FILTER_CR_CALLBACK, 'cbSpecialSkinLoot', NPC_TYPEFLAG_ENGINEERLOOT, null ] // salvageable [yn] ); protected $inputFields = array( 'cr' => [FILTER_V_LIST, [[1, 3],[5, 12], 15, 16, [18, 25], [27, 29], [31, 35], 37, 38, [40, 44]], true ], // criteria ids 'crs' => [FILTER_V_LIST, [FILTER_ENUM_NONE, FILTER_ENUM_ANY, [0, 9999]], true ], // criteria operators 'crv' => [FILTER_V_REGEX, parent::PATTERN_CRV, true ], // criteria values - only printable chars, no delimiter 'na' => [FILTER_V_REGEX, parent::PATTERN_NAME, false], // name / subname - only printable chars, no delimiter 'ex' => [FILTER_V_EQUAL, 'on', false], // also match subname 'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter 'fa' => [FILTER_V_CALLBACK, 'cbPetFamily', true ], // pet family [list] - cat[0] == 1 'minle' => [FILTER_V_RANGE, [1, 99], false], // min level [int] 'maxle' => [FILTER_V_RANGE, [1, 99], false], // max level [int] 'cl' => [FILTER_V_RANGE, [0, 4], true ], // classification [list] 'ra' => [FILTER_V_LIST, [-1, 0, 1], false], // react alliance [int] 'rh' => [FILTER_V_LIST, [-1, 0, 1], false] // react horde [int] ); protected function createSQLForValues() { $parts = []; $_v = &$this->fiData['v']; // name [str] if (isset($_v['na'])) { $_ = []; if (isset($_v['ex']) && $_v['ex'] == 'on') $_ = $this->modularizeString(['name_loc'.User::$localeId, 'subname_loc'.User::$localeId]); else $_ = $this->modularizeString(['name_loc'.User::$localeId]); if ($_) $parts[] = $_; } // pet family [list] if (isset($_v['fa'])) $parts[] = ['family', $_v['fa']]; // creatureLevel min [int] if (isset($_v['minle'])) $parts[] = ['minLevel', $_v['minle'], '>=']; // creatureLevel max [int] if (isset($_v['maxle'])) $parts[] = ['maxLevel', $_v['maxle'], '<=']; // classification [list] if (isset($_v['cl'])) $parts[] = ['rank', $_v['cl']]; // react Alliance [int] if (isset($_v['ra'])) $parts[] = ['ft.A', $_v['ra']]; // react Horde [int] if (isset($_v['rh'])) $parts[] = ['ft.H', $_v['rh']]; return $parts; } protected function cbPetFamily(&$val) { if (!$this->parentCats || $this->parentCats[0] != 1) return false; if (!Util::checkNumeric($val, NUM_CAST_INT)) return false; $type = FILTER_V_LIST; $valid = [[1, 9], 11, 12, 20, 21, [24, 27], [30, 35], [37, 39], [41, 46]]; return $this->checkInput($type, $valid, $val); } protected function cbRelEvent($cr) { if ($cr[1] == FILTER_ENUM_ANY) { $eventIds = DB::Aowow()->selectCol('SELECT id FROM ?_events WHERE holidayId <> 0'); $cGuids = DB::World()->selectCol('SELECT DISTINCT guid FROM game_event_creature WHERE eventEntry IN (?a)', $eventIds); return ['s.guid', $cGuids]; } else if ($cr[1] == FILTER_ENUM_NONE) { $eventIds = DB::Aowow()->selectCol('SELECT id FROM ?_events WHERE holidayId <> 0'); $cGuids = DB::World()->selectCol('SELECT DISTINCT guid FROM game_event_creature WHERE eventEntry IN (?a)', $eventIds); return ['s.guid', $cGuids, '!']; } else if (in_array($cr[1], $this->enums[$cr[0]])) { if ($eventIds = DB::Aowow()->selectCol('SELECT id FROM ?_events WHERE holidayId = ?d', $cr[1])) if ($cGuids = DB::World()->selectCol('SELECT DISTINCT guid FROM game_event_creature WHERE eventEntry IN (?a)', $eventIds)) return ['s.guid', $cGuids]; return [0]; } return false; } protected function cbMoneyDrop($cr) { if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1])) return false; return ['AND', ['((minGold + maxGold) / 2)', $cr[2], $cr[1]]]; } protected function cbQuestRelation($cr, $field, $val) { switch ($cr[1]) { case 1: // any return ['AND', ['qse.method', $val, '&'], ['qse.questId', null, '!']]; case 2: // alliance return ['AND', ['qse.method', $val, '&'], ['qse.questId', null, '!'], [['qt.reqRaceMask', RACE_MASK_HORDE, '&'], 0], ['qt.reqRaceMask', RACE_MASK_ALLIANCE, '&']]; case 3: // horde return ['AND', ['qse.method', $val, '&'], ['qse.questId', null, '!'], [['qt.reqRaceMask', RACE_MASK_ALLIANCE, '&'], 0], ['qt.reqRaceMask', RACE_MASK_HORDE, '&']]; case 4: // both return ['AND', ['qse.method', $val, '&'], ['qse.questId', null, '!'], ['OR', ['AND', ['qt.reqRaceMask', RACE_MASK_ALLIANCE, '&'], ['qt.reqRaceMask', RACE_MASK_HORDE, '&']], ['qt.reqRaceMask', 0]]]; case 5: // none $this->extraOpts['ct']['h'][] = $field.' = 0'; return [1]; } return false; } protected function cbHealthMana($cr, $minField, $maxField) { if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1])) return false; // remap OP for this special case switch ($cr[1]) { case '=': // min > max is totally possible $this->extraOpts['ct']['h'][] = $minField.' = '.$maxField.' AND '.$minField.' = '.$cr[2]; break; case '>': case '>=': case '<': case '<=': $this->extraOpts['ct']['h'][] = 'IF('.$minField.' > '.$maxField.', '.$maxField.', '.$minField.') '.$cr[1].' '.$cr[2]; break; } return [1]; // always true, use post-filter } protected function cbSpecialSkinLoot($cr, $typeFlag) { if (!$this->int2Bool($cr[1])) return false; if ($cr[1]) return ['AND', ['skinLootId', 0, '>'], ['typeFlags', $typeFlag, '&']]; else return ['OR', ['skinLootId', 0], [['typeFlags', $typeFlag, '&'], 0]]; } protected function cbRegularSkinLoot($cr, $typeFlag) { if (!$this->int2Bool($cr[1])) return false; if ($cr[1]) return ['AND', ['skinLootId', 0, '>'], [['typeFlags', $typeFlag, '&'], 0]]; else return ['OR', ['skinLootId', 0], ['typeFlags', $typeFlag, '&']]; } protected function cbReputation($cr, $op) { if (!in_array($cr[1], $this->enums[$cr[0]])) return false; if ($_ = DB::Aowow()->selectRow('SELECT * FROM ?_factions WHERE id = ?d', $cr[1])) $this->formData['reputationCols'][] = [$cr[1], Util::localizedString($_, 'name')]; if ($cIds = DB::World()->selectCol('SELECT creature_id FROM creature_onkill_reputation WHERE (RewOnKillRepFaction1 = ?d AND RewOnKillRepValue1 '.$op.' 0) OR (RewOnKillRepFaction2 = ?d AND RewOnKillRepValue2 '.$op.' 0)', $cr[1], $cr[1])) return ['id', $cIds]; else return [0]; } protected function cbFaction($cr) { if (!Util::checkNumeric($cr[1], NUM_CAST_INT)) return false; if (!in_array($cr[1], $this->enums[$cr[0]])) return false; $facTpls = []; $facs = new FactionList(array('OR', ['parentFactionId', $cr[1]], ['id', $cr[1]])); foreach ($facs->iterate() as $__) $facTpls = array_merge($facTpls, $facs->getField('templateIds')); return $facTpls ? ['faction', $facTpls] : [0]; } } ?>