diff --git a/pages/npc.php b/endpoints/npc/npc.php similarity index 63% rename from pages/npc.php rename to endpoints/npc/npc.php index da173275..38de19f9 100644 --- a/pages/npc.php +++ b/endpoints/npc/npc.php @@ -6,75 +6,80 @@ if (!defined('AOWOW_REVISION')) die('illegal access'); -// menuId 4: NPC g_initPath() -// tabId 0: Database g_initHeader() -class NpcPage extends GenericPage +class NpcBaseResponse extends TemplateResponse implements ICache { - use TrDetailPage; + use TrDetailPage, TrCache; - protected $placeholder = []; - protected $accessory = []; - protected $quotes = []; - protected $reputation = []; - protected $subname = ''; + protected int $cacheType = CACHE_TYPE_PAGE; - protected $type = Type::NPC; - protected $typeId = 0; - protected $tpl = 'npc'; - protected $path = [0, 4]; - protected $tabId = 0; - protected $mode = CACHE_TYPE_PAGE; - protected $scripts = [[SC_JS_FILE, 'js/swfobject.js'], [SC_CSS_FILE, 'css/Profiler.css']]; + protected string $template = 'npc'; + protected string $pageName = 'npc'; + protected ?int $activeTab = parent::TAB_DATABASE; + protected array $breadcrumb = [0, 4]; - protected $_get = ['domain' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\Locale::tryFromDomain']]; + protected array $scripts = [[SC_JS_FILE, 'js/swfobject.js'], [SC_CSS_FILE, 'css/Profiler.css']]; - private $soundIds = []; - private $powerTpl = '$WowheadPower.registerNpc(%d, %d, %s);'; + public int $type = Type::NPC; + public int $typeId = 0; + public array $placeholder = []; + public array $accessory = []; + public ?array $quotes = null; + public array $reputation = []; + public string $subname = ''; - public function __construct($pageCall, $id) + private CreatureList $subject; + private ?CreatureList $altNPCs = null; + private array $soundIds = []; + + public function __construct(string $id) { - parent::__construct($pageCall, $id); + parent::__construct($id); - // temp locale - if ($this->mode == CACHE_TYPE_TOOLTIP && $this->_get['domain']) - Lang::load($this->_get['domain']); - - $this->typeId = intVal($id); + $this->typeId = intVal($id); + $this->contribute = Type::getClassAttrib($this->type, 'contribute') ?? CONTRIBUTE_NONE; + } + protected function generate() : void + { $this->subject = new CreatureList(array(['id', $this->typeId])); if ($this->subject->error) - $this->notFound(Lang::game('npc'), Lang::npc('notFound')); + $this->generateNotFound(Lang::game('npc'), Lang::npc('notFound')); - $this->name = Util::htmlEscape($this->subject->getField('name', true)); - $this->subname = Util::htmlEscape($this->subject->getField('subname', true)); - } + $this->h1 = Util::htmlEscape($this->subject->getField('name', true)); + $this->subname = $this->subject->getField('subname', true); - protected function generatePath() - { - $this->path[] = $this->subject->getField('type'); - - if ($_ = $this->subject->getField('family')) - $this->path[] = $_; - } - - protected function generateTitle() - { - array_unshift($this->title, $this->subject->getField('name', true), Util::ucFirst(Lang::game('npc'))); - } - - protected function generateContent() - { - $this->addScript([SC_JS_FILE, '?data=zones']); + $this->gPageInfo += array( + 'type' => $this->type, + 'typeId' => $this->typeId, + 'name' => $this->subject->getField('name', true) + ); $_typeFlags = $this->subject->getField('typeFlags'); $_altIds = []; - $_altNPCs = null; - $placeholder = []; - $accessory = []; - // difficulty entries of self + + /*************/ + /* Menu Path */ + /*************/ + + $this->breadcrumb[] = $this->subject->getField('type'); + if ($_ = $this->subject->getField('family')) + $this->breadcrumb[] = $_; + + + /**************/ + /* Page Title */ + /**************/ + + array_unshift($this->title, $this->subject->getField('name', true), mb_strtoupper(Lang::game('npc'))); + + + /***********************/ + /* Difficulty versions */ + /***********************/ + if ($this->subject->getField('cuFlags') & NPC_CU_DIFFICULTY_DUMMY) - $placeholder = [$this->subject->getField('parentId'), $this->subject->getField('parent', true)]; + $this->placeholder = [$this->subject->getField('parentId'), $this->subject->getField('parent', true)]; else { for ($i = 1; $i < 4; $i++) @@ -82,36 +87,37 @@ class NpcPage extends GenericPage $_altIds[$_] = $i; if ($_altIds) - $_altNPCs = new CreatureList(array(['id', array_keys($_altIds)])); + $this->altNPCs = new CreatureList(array(['id', array_keys($_altIds)])); } if ($_ = DB::World()->selectCol('SELECT DISTINCT `entry` FROM vehicle_template_accessory WHERE `accessory_entry` = ?d', $this->typeId)) { $vehicles = new CreatureList(array(['id', $_])); foreach ($vehicles->iterate() as $id => $__) - $accessory[] = [$id, $vehicles->getField('name', true)]; + $this->accessory[] = [$id, $vehicles->getField('name', true)]; } - // try to determine, if it's spawned in a dungeon or raid (shaky at best, if spawned by script) + + /**********************/ + /* Determine Map Type */ + /**********************/ + $mapType = 0; if ($maps = DB::Aowow()->selectCol('SELECT DISTINCT `areaId` FROM ?_spawns WHERE `type` = ?d AND `typeId` = ?d', Type::NPC, $this->typeId)) { if (count($maps) == 1) // should only exist in one instance - { - switch (DB::Aowow()->selectCell('SELECT `type` FROM ?_zones WHERE `id` = ?d', $maps[0])) + $mapType = match (DB::Aowow()->selectCell('SELECT `type` FROM ?_zones WHERE `id` = ?d', $maps[0])) { - // case MAP_TYPE_DUNGEON: - case MAP_TYPE_DUNGEON_HC: - $mapType = 1; break; - // case MAP_TYPE_RAID: - case MAP_TYPE_MMODE_RAID: - case MAP_TYPE_MMODE_RAID_HC: - $mapType = 2; break; - } - } + // MAP_TYPE_DUNGEON, + MAP_TYPE_DUNGEON_HC => 1, + // MAP_TYPE_RAID, + MAP_TYPE_MMODE_RAID, + MAP_TYPE_MMODE_RAID_HC => 2, + default => 0 + }; } // npc is difficulty dummy: get max difficulty from parent npc - if ($placeholder && ($mt = DB::Aowow()->selectCell('SELECT IF(`difficultyEntry1` = ?d, 1, 2) FROM ?_creature WHERE `difficultyEntry1` = ?d OR `difficultyEntry2` = ?d OR `difficultyEntry3` = ?d', $this->typeId, $this->typeId, $this->typeId, $this->typeId))) + if ($this->placeholder && ($mt = DB::Aowow()->selectCell('SELECT IF(`difficultyEntry1` = ?d, 1, 2) FROM ?_creature WHERE `difficultyEntry1` = ?d OR `difficultyEntry2` = ?d OR `difficultyEntry3` = ?d', $this->typeId, $this->typeId, $this->typeId, $this->typeId))) $mapType = max($mapType, $mt); // npc has difficulty dummys: 2+ dummies -> definitely raid (10/25 + hc); 1 dummy -> may be heroic (used here), but may also be 10/25-raid if ($_altIds) @@ -135,7 +141,7 @@ class NpcPage extends GenericPage foreach ($_ as $i => $e) $ev[] = ($i % 2 ? '[br]' : ' ') . '[event='.$e.']'; - $infobox[] = Util::ucFirst(Lang::game('eventShort')).Lang::main('colon').implode(',', $ev); + $infobox[] = Lang::game('eventShort', [implode(',', $ev)]); } // Level @@ -155,38 +161,38 @@ class NpcPage extends GenericPage if ($_ = $this->subject->getField('rank')) // != NPC_RANK_NORMAL { $str = $this->subject->isBoss() ? '[span class=icon-boss]'.Lang::npc('rank', $_).'[/span]' : Lang::npc('rank', $_); - $infobox[] = Lang::npc('classification').Lang::main('colon').$str; + $infobox[] = Lang::npc('classification', [$str]); } // Reaction - $_ = function ($r) + $color = fn (int $r) : string => match($r) { - if ($r == 1) return 2; - if ($r == -1) return 10; - return; + 1 => 'q2', // q2 green + -1 => 'q10', // q10 red + default => 'q' // q yellow }; - $infobox[] = Lang::npc('react').Lang::main('colon').'[color=q'.$_($this->subject->getField('A')).']A[/color] [color=q'.$_($this->subject->getField('H')).']H[/color]'; + $infobox[] = Lang::npc('react', ['[color='.$color($this->subject->getField('A')).']A[/color] [color='.$color($this->subject->getField('H')).']H[/color]']); // Faction $this->extendGlobalIds(Type::FACTION, $this->subject->getField('factionId')); $infobox[] = Util::ucFirst(Lang::game('faction')).Lang::main('colon').'[faction='.$this->subject->getField('factionId').']'; // Tameable - if ($_typeFlags & 0x1) + if ($_typeFlags & NPC_TYPEFLAG_TAMEABLE) if ($_ = $this->subject->getField('family')) $infobox[] = Lang::npc('tameable', ['[url=pet='.$_.']'.Lang::game('fa', $_).'[/url]']); // Wealth if ($_ = intVal(($this->subject->getField('minGold') + $this->subject->getField('maxGold')) / 2)) - $infobox[] = Lang::npc('worth').Lang::main('colon').'[tooltip=tooltip_avgmoneydropped][money='.$_.'][/tooltip]'; + $infobox[] = Lang::npc('worth', ['[tooltip=tooltip_avgmoneydropped][money='.$_.'][/tooltip]']); // is Vehicle if ($this->subject->getField('vehicleId')) $infobox[] = Lang::npc('vehicle'); - // is visible as ghost + // is visible as ghost (redundant to extraFlags) if ($this->subject->getField('npcflag') & (NPC_FLAG_SPIRIT_HEALER | NPC_FLAG_SPIRIT_GUIDE)) - $infobox[] = Lang::npc('spirit'); + $infobox[] = Lang::npc('extraFlags', CREATURE_FLAG_EXTRA_GHOST_VISIBILITY); if (User::isInGroup(U_GROUP_EMPLOYEE)) { @@ -202,178 +208,60 @@ class NpcPage extends GenericPage $buff = []; for ($i = 0; $i < 31; $i++) if ($immuneMask & (1 << $i)) - $buff[] = (!fMod(count($buff), 3) ? "\n" : null).'[url=?spells&filter=me='.($i + 1).']'.Lang::game('me', $i + 1).'[/url]'; + $buff[] = (!fMod(count($buff), 3) ? "\n" : '').'[url=?spells&filter=me='.($i + 1).']'.Lang::game('me', $i + 1).'[/url]'; - $infobox[] = 'Not affected by mechanic'.Lang::main('colon').implode(', ', $buff); + $infobox[] = Lang::npc('mechanicimmune', [implode(', ', $buff)]); } // extra flags if ($flagsExtra = $this->subject->getField('flagsExtra')) { $buff = []; - if ($flagsExtra & 0x000001) - $buff[] = 'Binds attacker to instance on death'; - if ($flagsExtra & 0x000002) - $buff[] = "[tooltip name=civilian]- does not aggro\n- death costs Honor[/tooltip][span class=tip tooltip=civilian]Civilian[/span]"; - if ($flagsExtra & 0x000004) - $buff[] = 'Cannot parry'; - if ($flagsExtra & 0x000008) - $buff[] = 'Has no parry haste'; - if ($flagsExtra & 0x000010) - $buff[] = 'Cannot block'; - if ($flagsExtra & 0x000020) - $buff[] = 'Cannot deal Crushing Blows'; - if ($flagsExtra & 0x000040) - $buff[] = 'Rewards no experience'; - if ($flagsExtra & 0x000080) - $buff[] = 'Trigger creature'; - if ($flagsExtra & 0x000100) - $buff[] = 'Immune to Taunt'; - if ($flagsExtra & 0x008000) - $buff[] = "[tooltip name=guard]- engages PvP attackers\n- ignores enemy stealth, invisibility and Feign Death[/tooltip][span class=tip tooltip=guard]Guard[/span]"; - if ($flagsExtra & 0x020000) - $buff[] = 'Cannot deal Critical Hits'; - if ($flagsExtra & 0x040000) - $buff[] = 'Attacker does not gain weapon skill'; - if ($flagsExtra & 0x080000) - $buff[] = 'Taunt has diminishing returns'; - if ($flagsExtra & 0x100000) - $buff[] = 'Is subject to diminishing returns'; + foreach (Lang::npc('extraFlags') as $idx => $str) + if ($flagsExtra & $idx) + $buff[] = $str; if ($buff) - $infobox[] = 'Extra Flags'.Lang::main('colon').'[ul][li]'.implode('[/li][li]', $buff).'[/li][/ul]'; + $infobox[] = Lang::npc('_extraFlags').'[ul][li]'.implode('[/li][li]', $buff).'[/li][/ul]'; } // Mode dummy references - if ($_altNPCs) + if ($this->altNPCs) { - $this->extendGlobalData($_altNPCs->getJSGlobals()); - $buff = 'Difficulty Versions'.Lang::main('colon').'[ul]'; - foreach ($_altNPCs->iterate() as $id => $__) + $this->extendGlobalData($this->altNPCs->getJSGlobals()); + $buff = Lang::npc('versions').'[ul]'; + foreach ($this->altNPCs->iterate() as $id => $__) $buff .= '[li][npc='.$id.'][/li]'; $infobox[] = $buff.'[/ul]'; } } - // > Stats - $stats = []; - $modes = []; // get difficulty versions if set - $hint = '[tooltip name=%3$s][table cellspacing=10][tr]%1$s[/tr][/table][/tooltip][span class=tip tooltip=%3$s]%2$s[/span]'; - $modeRow = '[tr][td]%s  [/td][td]%s[/td][/tr]'; - // Health - $health = $this->subject->getBaseStats('health'); - $stats['health'] = Util::ucFirst(Lang::spell('powerTypes', -2)).Lang::main('colon').($health[0] < $health[1] ? Lang::nf($health[0]).' - '.Lang::nf($health[1]) : Lang::nf($health[0])); + 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]'; - // Mana (may be 0) - $mana = $this->subject->getBaseStats('power'); - $stats['mana'] = $mana[0] ? Lang::spell('powerTypes', 0).Lang::main('colon').($mana[0] < $mana[1] ? Lang::nf($mana[0]).' - '.Lang::nf($mana[1]) : Lang::nf($mana[0])) : null; - - // Armor - $armor = $this->subject->getBaseStats('armor'); - $stats['armor'] = Lang::npc('armor').Lang::main('colon').($armor[0] < $armor[1] ? Lang::nf($armor[0]).' - '.Lang::nf($armor[1]) : Lang::nf($armor[0])); - - // Resistances - $resNames = [null, 'hol', 'fir', 'nat', 'fro', 'sha', 'arc']; - $tmpRes = []; - $stats['resistance'] = ''; - foreach ($this->subject->getBaseStats('resistance') as $sc => $amt) - if ($amt) - $tmpRes[] = '[span class="moneyschool'.$resNames[$sc].'"]'.$amt.'[/span]'; - - if ($tmpRes) + if ($infobox) { - $stats['resistance'] = Lang::npc('resistances').Lang::main('colon'); - if (count($tmpRes) > 3) - $stats['resistance'] .= implode(' ', array_slice($tmpRes, 0, 3)).'[br]'.implode(' ', array_slice($tmpRes, 3)); - else - $stats['resistance'] .= implode(' ', $tmpRes); + $this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0'); + $this->extendGlobalData($this->infobox->getJsGlobals()); } - // Melee Damage - $melee = $this->subject->getBaseStats('melee'); - if ($_ = $this->subject->getField('dmgSchool')) // magic damage - $stats['melee'] = Lang::npc('melee').Lang::main('colon').Lang::nf($melee[0]).' - '.Lang::nf($melee[1]).' ('.Lang::game('sc', $_).')'; - else // phys. damage - $stats['melee'] = Lang::npc('melee').Lang::main('colon').Lang::nf($melee[0]).' - '.Lang::nf($melee[1]); - - // Ranged Damage - $ranged = $this->subject->getBaseStats('ranged'); - $stats['ranged'] = Lang::npc('ranged').Lang::main('colon').Lang::nf($ranged[0]).' - '.Lang::nf($ranged[1]); - - if (in_array($mapType, [1, 2])) // Dungeon or Raid - { - foreach ($_altIds as $id => $mode) - { - foreach ($_altNPCs->iterate() as $dId => $__) - { - if ($dId != $id) - continue; - - $m = Lang::npc('modes', $mapType, $mode); - - // Health - $health = $_altNPCs->getBaseStats('health'); - $modes['health'][] = sprintf($modeRow, $m, $health[0] < $health[1] ? Lang::nf($health[0]).' - '.Lang::nf($health[1]) : Lang::nf($health[0])); - - // Mana (may be 0) - $mana = $_altNPCs->getBaseStats('power'); - $modes['mana'][] = $mana[0] ? sprintf($modeRow, $m, $mana[0] < $mana[1] ? Lang::nf($mana[0]).' - '.Lang::nf($mana[1]) : Lang::nf($mana[0])) : null; - - // Armor - $armor = $_altNPCs->getBaseStats('armor'); - $modes['armor'][] = sprintf($modeRow, $m, $armor[0] < $armor[1] ? Lang::nf($armor[0]).' - '.Lang::nf($armor[1]) : Lang::nf($armor[0])); - - // Resistances - $tmpRes = ''; - foreach ($_altNPCs->getBaseStats('resistance') as $sc => $amt) - $tmpRes .= '[td]'.$amt.'[/td]'; - - if ($tmpRes) - { - if (!isset($modes['resistance'])) // init table head - $modes['resistance'][] = '[td][/td][td][span class="moneyschoolhol"]    [/span][/td][td][span class="moneyschoolfir"]    [/span][/td][td][span class="moneyschoolnat"]    [/span][/td][td][span class="moneyschoolfro"]    [/span][/td][td][span class="moneyschoolsha"]    [/span][/td][td][span class="moneyschoolarc"][/span][/td]'; - - if (!$stats['resistance']) // base creature has no resistance. -> display list item. - $stats['resistance'] = Lang::npc('resistances').Lang::main('colon').'…'; - - $modes['resistance'][] = '[td]'.$m.'    [/td]'.$tmpRes; - } - - // Melee Damage - $melee = $_altNPCs->getBaseStats('melee'); - if ($_ = $_altNPCs->getField('dmgSchool')) // magic damage - $modes['melee'][] = sprintf($modeRow, $m, Lang::nf($melee[0]).' - '.Lang::nf($melee[1]).' ('.Lang::game('sc', $_).')'); - else // phys. damage - $modes['melee'][] = sprintf($modeRow, $m, Lang::nf($melee[0]).' - '.Lang::nf($melee[1])); - - // Ranged Damage - $ranged = $_altNPCs->getBaseStats('ranged'); - $modes['ranged'][] = sprintf($modeRow, $m, Lang::nf($ranged[0]).' - '.Lang::nf($ranged[1])); - } - } - } - - if ($modes) - foreach ($stats as $k => $v) - if ($v) - $stats[$k] = sprintf($hint, implode('[/tr][tr]', $modes[$k]), $v, $k); - - // < Stats - if ($stats) - $infobox[] = Lang::npc('stats').($modes ? ' ('.Lang::npc('modes', $mapType, 0).')' : null).Lang::main('colon').'[ul][li]'.implode('[/li][li]', $stats).'[/li][/ul]'; - /****************/ /* Main Content */ /****************/ // get spawns and path - $map = null; if ($spawns = $this->subject->getSpawns(SPAWNINFO_FULL)) { - $map = ['data' => ['parent' => 'mapper-generic'], 'mapperData' => &$spawns, 'foundIn' => Lang::npc('foundIn')]; - foreach ($spawns as $areaId => &$areaData) - $map['extra'][$areaId] = ZoneList::getName($areaId); + $this->addDataLoader('zones'); + $this->map = array( + ['parent' => 'mapper-generic'], // Mapper + $spawns, // mapperData + null, // ShowOnMap + [Lang::npc('foundIn')] // foundIn + ); + foreach ($spawns as $areaId => $_) + $this->map[3][$areaId] = ZoneList::getName($areaId); } // smart AI @@ -394,19 +282,17 @@ class NpcPage extends GenericPage } if ($sai->prepare()) + { $this->extendGlobalData($sai->getJSGlobals()); + $this->smartAI = $sai->getMarkup(); + } else trigger_error('Creature has SmartAI set in template but no SmartAI defined.'); } // consider pooled spawns - $this->map = $map; - $this->infobox = '[ul][li]'.implode('[/li][li]', $infobox).'[/li][/ul]'; - $this->placeholder = $placeholder; - $this->accessory = $accessory; $this->quotes = $this->getQuotes(); $this->reputation = $this->getOnKillRep($_altIds, $mapType); - $this->smartAI = $sai ? $sai->getMarkup() : null; $this->redButtons = array( BUTTON_WOWHEAD => true, BUTTON_LINKS => ['type' => $this->type, 'typeId' => $this->typeId], @@ -421,6 +307,8 @@ class NpcPage extends GenericPage /* Extra Tabs */ /**************/ + $this->lvTabs = new Tabs(['parent' => "\$\$WH.ge('tabs-generic')"], 'tabsRelated', true); + // tab: abilities / tab_controlledabilities (dep: VehicleId) $tplSpells = []; $genSpells = []; @@ -436,7 +324,7 @@ class NpcPage extends GenericPage if ($smartSpells = SmartAI::getSpellCastsForOwner($this->typeId, SmartAI::SRC_TYPE_CREATURE)) $genSpells = $smartSpells; - if ($auras = DB::World()->selectCell('SELECT auras FROM creature_template_addon WHERE entry = ?d', $this->typeId)) + if ($auras = DB::World()->selectCell('SELECT `auras` FROM creature_template_addon WHERE `entry` = ?d', $this->typeId)) { $auras = preg_replace('/[^\d ]/', ' ', $auras); // remove erroneous chars from string $genSpells = array_merge($genSpells, array_filter(explode(' ', $auras))); @@ -446,16 +334,16 @@ class NpcPage extends GenericPage $conditions[] = ['id', $genSpells]; // Pet-Abilities - if ($_typeFlags & 0x1 && ($_ = $this->subject->getField('family'))) + if (($_typeFlags & NPC_TYPEFLAG_TAMEABLE) && ($_ = $this->subject->getField('family'))) { $skill = 0; $mask = 0x0; - foreach (Game::$skillLineMask[-1] as $idx => $pair) + foreach (Game::$skillLineMask[-1] as $idx => [$familyId, $skillLineId]) { - if ($pair[0] != $_) + if ($familyId != $_) continue; - $skill = $pair[1]; + $skill = $skillLineId; $mask = 1 << $idx; break; } @@ -496,24 +384,19 @@ class NpcPage extends GenericPage $this->extendGlobalData($cnd->getJsGlobals()); if ($normal) - $this->lvTabs[] = [SpellList::$brickFile, array( - 'data' => array_values($normal), + $this->lvTabs->addListviewTab(new Listview(array( + 'data' => $normal, 'name' => '$LANG.tab_abilities', 'id' => 'abilities' - )]; + ), SpellList::$brickFile)); if ($controled) - { - $lvTab = array( - 'data' => array_values($controled), - 'name' => '$LANG.tab_controlledabilities', - 'id' => 'controlled-abilities' - ); - if ($extraCols) - $lvTab['extraCols'] = $extraCols; - - $this->lvTabs[] = [SpellList::$brickFile, $lvTab]; - } + $this->lvTabs->addListviewTab(new Listview(array( + 'data' => $controled, + 'name' => '$LANG.tab_controlledabilities', + 'id' => 'controlled-abilities', + 'extraCols' => $extraCols ?: null + ), SpellList::$brickFile)); } } @@ -530,11 +413,11 @@ class NpcPage extends GenericPage { $this->extendGlobalData($sbSpell->getJSGlobals()); - $this->lvTabs[] = [SpellList::$brickFile, array( - 'data' => array_values($sbSpell->getListviewData()), + $this->lvTabs->addListviewTab(new Listview(array( + 'data' => $sbSpell->getListviewData(), 'name' => '$LANG.tab_summonedby', 'id' => 'summoned-by-spell' - )]; + ), SpellList::$brickFile)); } // tab: summoned by [NPC] @@ -546,11 +429,12 @@ class NpcPage extends GenericPage { $this->extendGlobalData($sbNPC->getJSGlobals()); - $this->lvTabs[] = [CreatureList::$brickFile, array( - 'data' => array_values($sbNPC->getListviewData()), + $this->addDataLoader('zones'); + $this->lvTabs->addListviewTab(new Listview(array( + 'data' => $sbNPC->getListviewData(), 'name' => '$LANG.tab_summonedby', 'id' => 'summoned-by-npc' - )]; + ), CreatureList::$brickFile)); } } @@ -562,23 +446,23 @@ class NpcPage extends GenericPage { $this->extendGlobalData($sbGO->getJSGlobals()); - $this->lvTabs[] = [GameObjectList::$brickFile, array( - 'data' => array_values($sbGO->getListviewData()), + $this->addDataLoader('zones'); + $this->lvTabs->addListviewTab(new Listview(array( + 'data' => $sbGO->getListviewData(), 'name' => '$LANG.tab_summonedby', 'id' => 'summoned-by-object' - )]; + ), GameObjectList::$brickFile)); } } // tab: teaches if ($this->subject->getField('npcflag') & NPC_FLAG_TRAINER) { - $teachQuery = ' - SELECT ts.SpellId AS ARRAY_KEY, ts.MoneyCost AS cost, ts.ReqSkillLine AS reqSkillId, ts.ReqSkillRank AS reqSkillValue, ts.ReqLevel AS reqLevel, ts.ReqAbility1 AS reqSpellId1, ts.reqAbility2 AS reqSpellId2 - FROM trainer_spell ts - JOIN creature_default_trainer cdt ON cdt.TrainerId = ts.TrainerId - WHERE cdt.Creatureid = ?d - '; + $teachQuery = + 'SELECT ts.`SpellId` AS ARRAY_KEY, ts.`MoneyCost` AS "cost", ts.`ReqSkillLine` AS "reqSkillId", ts.`ReqSkillRank` AS "reqSkillValue", ts.`ReqLevel` AS "reqLevel", ts.`ReqAbility1` AS "reqSpellId1", ts.`reqAbility2` AS "reqSpellId2" + FROM trainer_spell ts + JOIN creature_default_trainer cdt ON cdt.`TrainerId` = ts.`TrainerId` + WHERE cdt.`Creatureid` = ?d'; if ($tSpells = DB::World()->select($teachQuery, $this->typeId)) { @@ -618,17 +502,13 @@ class NpcPage extends GenericPage if ($cnd->toListviewColumn($data, $extraCols)) $this->extendGlobalData($cnd->getJsGlobals()); - $tabData = array( - 'data' => array_values($data), + $this->lvTabs->addListviewTab(new Listview(array( + 'data' => $data, 'name' => '$LANG.tab_teaches', 'id' => 'teaches', - 'visibleCols' => ['trainingcost'] - ); - - if ($extraCols) - $tabData['extraCols'] = array_values($extraCols); - - $this->lvTabs[] = [SpellList::$brickFile, $tabData]; + 'visibleCols' => ['trainingcost'], + 'extraCols' => $extraCols ?: null + ), SpellList::$brickFile)); } } else @@ -667,12 +547,12 @@ class NpcPage extends GenericPage $cnd->toListviewColumn($lvData, $extraCols, $this->typeId, 'id'); } - $this->lvTabs[] = [ItemList::$brickFile, array( - 'data' => array_values($lvData), + $this->lvTabs->addListviewTab(new Listview(array( + 'data' => $lvData, 'name' => '$LANG.tab_sells', 'id' => 'currency-for', 'extraCols' => array_unique($extraCols) - ), $colAddIn]; + ), ItemList::$brickFile, $colAddIn)); $this->extendGlobalData($soldItems->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED)); } @@ -715,12 +595,12 @@ class NpcPage extends GenericPage if ($_altIds) { $sourceFor[0][2] = $mapType == 1 ? $langref[-1] : $langref[1]; - foreach ($_altNPCs->iterate() as $id => $__) + 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 = $_altNPCs->getField('lootId')) + if ($lootId = $this->altNPCs->getField('lootId')) $sourceFor[($mode - 1) * 2] = [LOOT_CREATURE, $lootId, $langref[$mode], 'drops-'.abs($mode), [], '']; } } @@ -741,11 +621,12 @@ class NpcPage extends GenericPage $this->extendGlobalData($creatureLoot->jsGlobals); $tabData = array( - 'data' => array_values($creatureLoot->getResult()), - 'name' => $tabName, - 'id' => $tabId, - 'extraCols' => array_values(array_unique($extraCols)), - 'sort' => ['-percent', 'name'] + 'data' => $creatureLoot->getResult(), + 'name' => $tabName, + 'id' => $tabId, + 'extraCols' => array_unique($extraCols), + 'hiddenCols' => $hiddenCols ?: null, + 'sort' => ['-percent', 'name'] ); if ($note) @@ -753,10 +634,7 @@ class NpcPage extends GenericPage else if ($lootTpl == LOOT_SKINNING) $tabData['note'] = ''.Lang::formatSkillBreakpoints(Game::getBreakpointsForSkill($skinTab[2], $this->subject->getField('maxLevel') * 5), Lang::FMT_HTML).''; - if ($hiddenCols) - $tabData['hiddenCols'] = $hiddenCols; - - $this->lvTabs[] = [ItemList::$brickFile, $tabData]; + $this->lvTabs->addListviewTab(new Listview($tabData, ItemList::$brickFile)); } } @@ -767,30 +645,29 @@ class NpcPage extends GenericPage { $this->extendGlobalData($startEnd->getJSGlobals()); $lvData = $startEnd->getListviewData(); - $_ = [[], []]; + $start = $end = []; foreach ($startEnd->iterate() as $id => $__) { - $m = $startEnd->getField('method'); - if ($m & 0x1) - $_[0][] = $lvData[$id]; - if ($m & 0x2) - $_[1][] = $lvData[$id]; + if ($startEnd->getField('method') & 0x1) + $start[] = $lvData[$id]; + if ($startEnd->getField('method') & 0x2) + $end[] = $lvData[$id]; } - if ($_[0]) - $this->lvTabs[] = [QuestList::$brickFile, array( - 'data' => array_values($_[0]), + if ($start) + $this->lvTabs->addListviewTab(new Listview(array( + 'data' => $start, 'name' => '$LANG.tab_starts', 'id' => 'starts' - )]; + ), QuestList::$brickFile)); - if ($_[1]) - $this->lvTabs[] = [QuestList::$brickFile, array( - 'data' => array_values($_[1]), + if ($end) + $this->lvTabs->addListviewTab(new Listview(array( + 'data' => $end, 'name' => '$LANG.tab_ends', 'id' => 'ends' - )]; + ), QuestList::$brickFile)); } // tab: objective of quest @@ -807,11 +684,11 @@ class NpcPage extends GenericPage { $this->extendGlobalData($objectiveOf->getJSGlobals()); - $this->lvTabs[] = [QuestList::$brickFile, array( - 'data' => array_values($objectiveOf->getListviewData()), + $this->lvTabs->addListviewTab(new Listview(array( + 'data' => $objectiveOf->getListviewData(), 'name' => '$LANG.tab_objectiveof', 'id' => 'objective-of' - )]; + ), QuestList::$brickFile)); } // tab: criteria of [ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE_TYPE have no data set to check for] @@ -825,15 +702,15 @@ class NpcPage extends GenericPage { $this->extendGlobalData($crtOf->getJSGlobals()); - $this->lvTabs[] = [AchievementList::$brickFile, array( - 'data' => array_values($crtOf->getListviewData()), + $this->lvTabs->addListviewTab(new Listview(array( + 'data' => $crtOf->getListviewData(), 'name' => '$LANG.tab_criteriaof', 'id' => 'criteria-of' - )]; + ), AchievementList::$brickFile)); } // tab: passengers - if ($_ = DB::World()->selectCol('SELECT `accessory_entry` AS ARRAY_KEY, GROUP_CONCAT(`seat_id`) FROM vehicle_template_accessory WHERE `entry` = ?d GROUP BY `accessory_entry`', $this->typeId)) + if ($_ = DB::World()->selectCol('SELECT `accessory_entry` AS ARRAY_KEY, GROUP_CONCAT(`seat_id` SEPARATOR ", ") FROM vehicle_template_accessory WHERE `entry` = ?d GROUP BY `accessory_entry`', $this->typeId)) { $passengers = new CreatureList(array(['id', array_keys($_)])); if (!$passengers->error) @@ -842,12 +719,12 @@ class NpcPage extends GenericPage if (User::isInGroup(U_GROUP_STAFF)) foreach ($data as $id => &$d) - $d['seat'] = str_replace(',', ', ', $_[$id]); + $d['seat'] = $_[$id]; $this->extendGlobalData($passengers->getJSGlobals(GLOBALINFO_SELF)); $tabData = array( - 'data' => array_values($data), + 'data' => $data, 'name' => Lang::npc('accessory'), 'id' => 'accessory' ); @@ -855,7 +732,8 @@ class NpcPage extends GenericPage if (User::isInGroup(U_GROUP_STAFF)) $tabData['extraCols'] = ["\$Listview.funcBox.createSimpleCol('seat', '".Lang::npc('seat')."', '10%', 'seat')"]; - $this->lvTabs[] = [CreatureList::$brickFile, $tabData]; + $this->addDataLoader('zones'); + $this->lvTabs->addListviewTab(new Listview($tabData, CreatureList::$brickFile)); } } @@ -882,12 +760,11 @@ class NpcPage extends GenericPage if (isset($data[$id])) $data[$id]['activity'] = $activity; // no index, js wants a string :( - $tabData = ['data' => array_values($data)]; - if ($activitySounds) - $tabData['visibleCols'] = ['activity']; - - $this->extendGlobalData($sounds->getJSGlobals(GLOBALINFO_SELF)); - $this->lvTabs[] = [SoundList::$brickFile, $tabData]; + $this->extendGlobalData($sounds->getJSGlobals(GLOBALINFO_SELF)); + $this->lvTabs->addListviewTab(new Listview(array( + 'data' => $data, + 'visibleCols' => $activitySounds ? 'activity' : null + ), SoundList::$brickFile)); } } @@ -900,21 +777,10 @@ class NpcPage extends GenericPage if ($tab = $cnd->toListviewTab()) { $this->extendGlobalData($cnd->getJsGlobals()); - $this->lvTabs[] = $tab; - } - } - - protected function generateTooltip() - { - $power = new \StdClass(); - if (!$this->subject->error) - { - $power->{'name_'.Lang::getLocale()->json()} = $this->subject->getField('name', true); - $power->{'tooltip_'.Lang::getLocale()->json()} = $this->subject->renderTooltip(); - $power->map = $this->subject->getSpawns(SPAWNINFO_SHORT); + $this->lvTabs->addDataTab(...$tab); } - return sprintf($this->powerTpl, $this->typeId, Lang::getLocale()->value, Util::toJSON($power, JSON_AOWOW_POWER)); + parent::generate(); } private function getRepForId(array $entries, array &$spillover) : array @@ -936,24 +802,30 @@ class NpcPage extends GenericPage continue; $set = array( - 'id' => $row['faction'], - 'qty' => [$row['qty'], 0], - 'name' => $factions->getField('name', true), - 'npc' => $row['npc'], - 'cap' => $row['maxRank'] && $row['maxRank'] < REP_EXALTED ? Lang::game('rep', $row['maxRank']) : null + $row['faction'], // factionId + [$row['qty'], 0], // qty + $factions->getField('name', true), // name + $row['maxRank'] && $row['maxRank'] < REP_EXALTED ? Lang::game('rep', $row['maxRank']) : null, // cap + $row['npc'], // npcId + 0 // spilloverCat ); $cuRate = DB::World()->selectCell('SELECT `creature_rate` FROM reputation_reward_rate WHERE `creature_rate` <> 1 AND `faction` = ?d', $row['faction']); - if ($cuRate !== null) - $set['qty'][1] = $set['qty'][0] * ($cuRate - 1); + if ($cuRate && User::isInGroup(U_GROUP_EMPLOYEE)) + $set[1][1] = $set[1][0] . sprintf(Util::$dfnString, Lang::faction('customRewRate'), ($set[1][0] > 0 ? '+' : '').($set[1][0] * ($cuRate - 1))); + else if ($cuRate) + $set[1][1] = $set[1][0] * $cuRate; if ($row['spillover']) { - $spillover[$factions->getField('cat')] = array( - [ $set['qty'][0] / 2, $set['qty'][1] / 2 ], - $row['maxRank'] - ); - $set['spillover'] = $factions->getField('cat'); + $spill = [[$set[1][0] / 2, 0], $row['maxRank']]; + if ($cuRate && User::isInGroup(U_GROUP_EMPLOYEE)) + $spill[0][1] = $spill[0][0] . sprintf(Util::$dfnString, Lang::faction('customRewRate'), ($set[1][0] > 0 ? '+' : '').($spill[0][0] * ($cuRate - 1) * 0.5)); + else if ($cuRate) + $spill[0][1] = $set[1][1] / 2; + + $spillover[$factions->getField('cat')] = $spill; + $set[6] = $factions->getField('cat'); // set spillover } $result[] = $set; @@ -978,8 +850,8 @@ class NpcPage extends GenericPage $rep = $this->getRepForId(array_keys($dummyIds), $spilledParents); // order by difficulty - foreach ($rep as $r) - $alt[$dummyIds[$r['npc']]][] = $r; + foreach ($rep as $i => [, , , , $npcId]) + $alt[$dummyIds[$npcId]][] = $rep[$i]; // apply by difficulty foreach ($alt as $mode => $dat) @@ -991,30 +863,30 @@ class NpcPage extends GenericPage { $spilled = new FactionList(array(['parentFactionId', array_keys($spilledParents)])); - foreach ($reputation as &$sets) + foreach ($reputation as $i => [, $data]) { - foreach ($sets[1] as &$row) + foreach ($data as [$factionId, , , , , , $spillover]) { - if (empty($row['spillover'])) + if (!$spillover) continue; foreach ($spilled->iterate() as $spId => $__) { // find parent - if ($spilled->getField('parentFactionId') != $row['spillover']) + if ($spilled->getField('parentFactionId') != $spillover) continue; // don't readd parent - if ($row['id'] == $spId) + if ($factionId == $spId) continue; - $spMax = $spilledParents[$row['spillover']][1]; + $spMax = $spilledParents[$spillover][1]; - $sets[1][] = array( - 'id' => $spId, - 'qty' => $spilledParents[$row['spillover']][0], - 'name' => $spilled->getField('name', true), - 'cap' => $spMax && $spMax < REP_EXALTED ? Lang::game('rep', $spMax) : null + $reputation[$i][1][] = array( + $spId, + $spilledParents[$spillover][0], + $spilled->getField('name', true), + $spMax && $spMax < REP_EXALTED ? Lang::game('rep', $spMax) : null ); } } @@ -1024,14 +896,115 @@ class NpcPage extends GenericPage return $reputation; } - private function getQuotes() : array + private function getQuotes() : ?array { [$quotes, $nQuotes, $soundIds] = Game::getQuotesForCreature($this->typeId, true, $this->subject->getField('name', true)); if ($soundIds) $this->soundIds = array_merge($this->soundIds, $soundIds); - return [$quotes, $nQuotes]; + return $quotes ? [$quotes, $nQuotes] : null; + } + + private function getCreatureStats(int $mapType, array $altIds) : array + { + $stats = []; + $modes = []; // get difficulty versions if set + $hint = '[tooltip name=%3$s][table cellspacing=10][tr]%1s[/tr][/table][/tooltip][span class=tip tooltip=%3$s]%2s[/span]'; + $modeRow = '[tr][td]%s  [/td][td]%s[/td][/tr]'; + // Health + $health = $this->subject->getBaseStats('health'); + $stats['health'] = Util::ucFirst(Lang::spell('powerTypes', -2)).Lang::main('colon').($health[0] < $health[1] ? Lang::nf($health[0]).' - '.Lang::nf($health[1]) : Lang::nf($health[0])); + + // Mana (may be 0) + $mana = $this->subject->getBaseStats('power'); + $stats['mana'] = $mana[0] ? Lang::spell('powerTypes', 0).Lang::main('colon').($mana[0] < $mana[1] ? Lang::nf($mana[0]).' - '.Lang::nf($mana[1]) : Lang::nf($mana[0])) : ''; + + // Armor + $armor = $this->subject->getBaseStats('armor'); + $stats['armor'] = Lang::npc('armor').($armor[0] < $armor[1] ? Lang::nf($armor[0]).' - '.Lang::nf($armor[1]) : Lang::nf($armor[0])); + + // Resistances + $resNames = [null, 'hol', 'fir', 'nat', 'fro', 'sha', 'arc']; + $tmpRes = []; + $stats['resistance'] = ''; + foreach ($this->subject->getBaseStats('resistance') as $sc => $amt) + if ($amt) + $tmpRes[] = '[span class="moneyschool'.$resNames[$sc].'"]'.$amt.'[/span]'; + + if ($tmpRes) + { + $stats['resistance'] = Lang::npc('resistances').'[br]'; + if (count($tmpRes) > 3) + $stats['resistance'] .= implode(' ', array_slice($tmpRes, 0, 3)).'[br]'.implode(' ', array_slice($tmpRes, 3)); + else + $stats['resistance'] .= implode(' ', $tmpRes); + } + + // Melee Damage + $melee = $this->subject->getBaseStats('melee'); + if ($_ = $this->subject->getField('dmgSchool')) // magic damage + $stats['melee'] = Lang::npc('melee').Lang::nf($melee[0]).' - '.Lang::nf($melee[1]).' ('.Lang::game('sc', $_).')'; + else // phys. damage + $stats['melee'] = Lang::npc('melee').Lang::nf($melee[0]).' - '.Lang::nf($melee[1]); + + // Ranged Damage + $ranged = $this->subject->getBaseStats('ranged'); + $stats['ranged'] = Lang::npc('ranged').Lang::nf($ranged[0]).' - '.Lang::nf($ranged[1]); + + foreach ($altIds as $id => $mode) + { + if (!$this->altNPCs->getEntry($id)) + continue; + + $m = Lang::npc('modes', $mapType, $mode); + + // Health + $health = $this->altNPCs->getBaseStats('health'); + $modes['health'][] = sprintf($modeRow, $m, $health[0] < $health[1] ? Lang::nf($health[0]).' - '.Lang::nf($health[1]) : Lang::nf($health[0])); + + // Mana (may be 0) + $mana = $this->altNPCs->getBaseStats('power'); + $modes['mana'][] = $mana[0] ? sprintf($modeRow, $m, $mana[0] < $mana[1] ? Lang::nf($mana[0]).' - '.Lang::nf($mana[1]) : Lang::nf($mana[0])) : null; + + // Armor + $armor = $this->altNPCs->getBaseStats('armor'); + $modes['armor'][] = sprintf($modeRow, $m, $armor[0] < $armor[1] ? Lang::nf($armor[0]).' - '.Lang::nf($armor[1]) : Lang::nf($armor[0])); + + // Resistances + if (array_filter($this->altNPCs->getBaseStats('resistance'))) + { + if (!isset($modes['resistance'])) // init table head + $modes['resistance'][] = '[td][/td][td][span class="moneyschoolhol" style="margin: 0px 5px"][/span][/td][td][span class="moneyschoolfir" style="margin: 0px 5px"][/span][/td][td][span class="moneyschoolnat" style="margin: 0px 5px"][/span][/td][td][span class="moneyschoolfro" style="margin: 0px 5px"][/span][/td][td][span class="moneyschoolsha" style="margin: 0px 5px"][/span][/td][td][span class="moneyschoolarc" style="margin: 0px 5px"][/span][/td]'; + + if (!$stats['resistance']) // base creature has no resistance. -> display list item. + $stats['resistance'] = Lang::npc('resistances').'…'; + + $tmpRes = ''; + foreach ($this->altNPCs->getBaseStats('resistance') as $sc => $amt) + $tmpRes .= '[td][span style="margin: 0px 5px"]'.$amt.'[/span][/td]'; + + $modes['resistance'][] = '[td]'.$m.'    [/td]'.$tmpRes; + } + + // Melee Damage + $melee = $this->altNPCs->getBaseStats('melee'); + if ($_ = $this->altNPCs->getField('dmgSchool')) // magic damage + $modes['melee'][] = sprintf($modeRow, $m, Lang::nf($melee[0]).' - '.Lang::nf($melee[1]).' ('.Lang::game('sc', $_).')'); + else // phys. damage + $modes['melee'][] = sprintf($modeRow, $m, Lang::nf($melee[0]).' - '.Lang::nf($melee[1])); + + // Ranged Damage + $ranged = $this->altNPCs->getBaseStats('ranged'); + $modes['ranged'][] = sprintf($modeRow, $m, Lang::nf($ranged[0]).' - '.Lang::nf($ranged[1])); + } + + if ($modes) + foreach ($stats as $k => $v) + if ($v) + $stats[$k] = sprintf($hint, implode('[/tr][tr]', $modes[$k]), $v, $k); + + return $stats; } } diff --git a/endpoints/npc/npc_power.php b/endpoints/npc/npc_power.php new file mode 100644 index 00000000..56fb95af --- /dev/null +++ b/endpoints/npc/npc_power.php @@ -0,0 +1,50 @@ + ['filter' => FILTER_CALLBACK, 'options' => [Locale::class, 'tryFromDomain']] + ); + + public function __construct(string $id) + { + parent::__construct($id); + + // temp locale + if ($this->_get['domain']) + Lang::load($this->_get['domain']); + + $this->typeId = intVal($id); + } + + protected function generate() : void + { + $creature = new CreatureList(array(['id', $this->typeId])); + if ($creature->error) + $this->cacheType = CACHE_TYPE_NONE; + else + $opts = array( + 'name' => $creature->getField('name', true), + 'tooltip' => $creature->renderTooltip(), + 'map' => $creature->getSpawns(SPAWNINFO_SHORT) + ); + + $this->result = new Tooltip(self::POWER_TEMPLATE, $this->typeId, $opts ?? []); + } +} + +?> diff --git a/endpoints/npcs/npcs.php b/endpoints/npcs/npcs.php new file mode 100644 index 00000000..1bd05bfb --- /dev/null +++ b/endpoints/npcs/npcs.php @@ -0,0 +1,133 @@ + ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => Filter::PATTERN_PARAM]] + ); + protected array $validCats = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; + + public bool $petFamPanel = false; + + public function __construct(string $pageParam) + { + $this->getCategoryFromUrl($pageParam); + + parent::__construct($pageParam); + + $this->subCat = $pageParam !== '' ? '='.$pageParam : ''; + $this->filter = new CreatureListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]); + $this->filterError = $this->filter->error; + } + + protected function generate() : void + { + $this->h1 = Lang::game('npcs'); + + $conditions = []; + if (!User::isInGroup(U_GROUP_EMPLOYEE)) + $conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0]; + + $this->filter->evalCriteria(); + + if ($_ = $this->filter->getConditions()) + $conditions[] = $_; + + $this->filterError = $this->filter->error; // maybe the evalX() caused something + + if ($this->category) + { + $conditions[] = ['type', $this->category[0]]; + $this->petFamPanel = $this->category[0] == 1; + } + + $fiForm = $this->filter->values; + $fiRepCols = $this->filter->fiReputationCols; + + + /*************/ + /* Menu Path */ + /*************/ + + if ($this->category) + $this->breadcrumb[] = $this->category[0]; + + if (count($fiForm['fa']) == 1) + $this->breadcrumb[] = $fiForm['fa'][0]; + + + /**************/ + /* Page Title */ + /**************/ + + array_unshift($this->title, $this->h1); + if ($this->category) + array_unshift($this->title, Lang::npc('cat', $this->category[0])); + + if (count($fiForm['fa']) == 1) + array_unshift($this->title, Lang::game('fa', $fiForm['fa'][0])); + + + /****************/ + /* Main Content */ + /****************/ + + $this->redButtons[BUTTON_WOWHEAD] = true; + + // beast subtypes are selected via filter + $tabData = ['data' => []]; + $npcs = new CreatureList($conditions, ['extraOpts' => $this->filter->extraOpts, 'calcTotal' => true]); + if (!$npcs->error) + { + $tabData['data'] = $npcs->getListviewData($fiRepCols ? NPCINFO_REP : 0x0); + if ($fiRepCols) // never use pretty-print + $tabData['extraCols'] = '$fi_getReputationCols('.Util::toJSON($fiRepCols, JSON_NUMERIC_CHECK | JSON_UNESCAPED_UNICODE).')'; + else if ($this->filter->fiExtraCols) + $tabData['extraCols'] = '$fi_getExtraCols(fi_extraCols, 0, 0)'; + + if ($this->category) + $tabData['hiddenCols'] = ['type']; + + // create note if search limit was exceeded + if ($npcs->getMatches() > Cfg::get('SQL_LIMIT_DEFAULT')) + { + $tabData['note'] = sprintf(Util::$tryFilteringString, 'LANG.lvnote_npcsfound', $npcs->getMatches(), Cfg::get('SQL_LIMIT_DEFAULT')); + $tabData['_truncated'] = 1; + } + } + + $this->lvTabs = new Tabs(['parent' => "\$\$WH.ge('tabs-generic')"]); + + $this->lvTabs->addListviewTab(new Listview($tabData, CreatureList::$brickFile)); + + parent::generate(); + + $this->setOnCacheLoaded([self::class, 'onBeforeDisplay']); + } + + public static function onBeforeDisplay() : void + { + // sort for dropdown-menus + Lang::sort('game', 'fa'); + } +} + +?> diff --git a/localization/locale_dede.php b/localization/locale_dede.php index dce05d01..e37a8832 100644 --- a/localization/locale_dede.php +++ b/localization/locale_dede.php @@ -1040,35 +1040,37 @@ $lang = array( ), 'npc' => array( 'notFound' => "Dieser NPC existiert nicht.", - 'classification'=> "Einstufung", - 'petFamily' => "Tierart", - 'react' => "Reaktion", - 'worth' => "Wert", + 'classification'=> "Einstufung: %s", + 'petFamily' => "Tierart: ", + 'react' => "Reaktion: %s", + 'worth' => "Wert: %s", 'unkPosition' => "Der Aufenthaltsort dieses NPCs ist nicht bekannt.", 'difficultyPH' => 'Dieser NPC ist ein Platzhalter für einen anderen Modus von %2$s.', 'seat' => "Sitz", 'accessory' => "Zusätze", 'accessoryFor' => "Dieser NPC ist Zusatz für Fahrzeug", - 'quotes' => "Zitate", - 'gainsDesc' => "Nach dem Töten dieses NPCs erhaltet Ihr", + 'quotes' => "Zitate (%d)", + 'gainsDesc' => "Nach dem Töten dieses NPCs erhaltet Ihr: ", 'repWith' => "Ruf mit der Fraktion", 'stopsAt' => "Endet bei %s", 'vehicle' => "Fahrzeug", 'stats' => "Werte", - 'melee' => "Nahkampf", - 'ranged' => "Fernkampf", - 'armor' => "Rüstung", - 'resistances' => "Widerstände", + 'melee' => "Nahkampf: ", + 'ranged' => "Fernkampf: ", + 'armor' => "Rüstung: ", + 'resistances' => "Widerstände: ", 'foundIn' => "Dieser NPC befindet sich in", 'tameable' => "Zähmbar (%s)", - 'spirit' => "[tooltip name=spirit]Nur für tote Spieler sichtbar[/tooltip][span class=tip tooltip=spirit]Geist[/span]", 'waypoint' => "Wegpunkt", 'wait' => "Wartezeit", 'respawnIn' => "Respawn in: %s", - 'despawnAfter' => "Gespawnt durch Script
Despawn nach: %s", + 'despawnAfter' => "Gespawnt durch Script
Despawn nach: %s", 'rank' => [0 => "Normal", 1 => "Elite", 4 => "Rar", 2 => "Rar Elite", 3 => "Boss"], 'textRanges' => [null, "an das Gebiet gesendet", "an die Zone gesendet", "an die Map gesendet", "an die Welt gesendet"], 'textTypes' => [null, "schreit", "sagt", "flüstert"], + '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"] @@ -1102,6 +1104,33 @@ $lang = array( NPC_FLAG_GUILD_BANK => 'Gildenbank', NPC_FLAG_SPELLCLICK => 'Zauber-Klick', NPC_FLAG_MAILBOX => 'Briefkasten' + ), + 'extraFlags' => array( + CREATURE_FLAG_EXTRA_INSTANCE_BIND => 'Bindet Angreifer im Tod an die Instanz', + CREATURE_FLAG_EXTRA_CIVILIAN => "[tooltip name=civilian]- hat keine Aggro\n- Tod kostet Ehre[/tooltip][span class=tip tooltip=civilian]Zivilist[/span]", + CREATURE_FLAG_EXTRA_NO_PARRY => 'Kann nicht [spell=3127]', + CREATURE_FLAG_EXTRA_NO_PARRY_HASTEN => 'Erhält keine Eile nach [spell=3127]', + CREATURE_FLAG_EXTRA_NO_BLOCK => 'Kann nicht [spell=107]', + CREATURE_FLAG_EXTRA_NO_CRUSHING_BLOWS => 'Kann keine schmetternden Schläge verursachen', + CREATURE_FLAG_EXTRA_NO_XP => 'Belohnt keine Erfahrung', + CREATURE_FLAG_EXTRA_TRIGGER => 'Auslöser NPC', + CREATURE_FLAG_EXTRA_NO_TAUNT => 'Immun gegen Spott', + // CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE => '', // ?? + CREATURE_FLAG_EXTRA_GHOST_VISIBILITY => '[tooltip name=spirit]Nur für tote Spieler sichtbar[/tooltip][span class=tip tooltip=spirit]Geist[/span]', + CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK => 'Benutzt [spell=674]', + CREATURE_FLAG_EXTRA_NO_SELL_VENDOR => 'Händler kauft nicht vom Spieler', + CREATURE_FLAG_EXTRA_IGNORE_COMBAT => 'Kann nicht in einen Kampf verwickelt werden', + CREATURE_FLAG_EXTRA_WORLDEVENT => 'Gehört zu Weltereignis', + CREATURE_FLAG_EXTRA_GUARD => "[tooltip name=guard]- greift PvP-Angreifer an\n- ignoriert Unsichtbarkeit, Verstohlenheit und [spell=5384][/tooltip][span class=tip tooltip=guard]Wache[/span]", + CREATURE_FLAG_EXTRA_IGNORE_FEIGN_DEATH => 'Ignoriert [spell=5384]', + CREATURE_FLAG_EXTRA_NO_CRIT => 'Kann keine kritischen Treffer verursachen', + CREATURE_FLAG_EXTRA_NO_SKILL_GAINS => 'Angreifer erhält keine Waffenfertigkeit', + CREATURE_FLAG_EXTRA_OBEYS_TAUNT_DIMINISHING_RETURNS => 'Spott hat abnehmende Wirkung', + CREATURE_FLAG_EXTRA_ALL_DIMINISH => 'Alle Mechaniken haben abnehmende Wirkung', + CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ => 'Angreifender Spieler ist immer lootberechtigt', + // CREATURE_FLAG_EXTRA_DUNGEON_BOSS => '', // set during runtime + CREATURE_FLAG_EXTRA_IGNORE_PATHFINDING => 'Ignoriert Wegfindung', + CREATURE_FLAG_EXTRA_IMMUNITY_KNOCKBACK => 'Immung gegen Rückstoß' ) ), 'event' => array( diff --git a/localization/locale_enus.php b/localization/locale_enus.php index ee6d65f6..6670ad1b 100644 --- a/localization/locale_enus.php +++ b/localization/locale_enus.php @@ -1040,35 +1040,37 @@ $lang = array( ), 'npc' => array( 'notFound' => "This NPC doesn't exist.", - 'classification'=> "Classification", - 'petFamily' => "Pet familiy", - 'react' => "React", - 'worth' => "Worth", + 'classification'=> "Classification: %s", + 'petFamily' => "Pet familiy: ", + 'react' => "React: %s", + 'worth' => "Worth: %s", 'unkPosition' => "The location of this NPC is unknown.", 'difficultyPH' => 'This NPC is a placeholder for a different mode of %2$s.', 'seat' => "Seat", 'accessory' => "Accessories", 'accessoryFor' => "This NPC is an accessory for vehicle", - 'quotes' => "Quotes", - 'gainsDesc' => "After killing this NPC you will gain", + 'quotes' => "Quotes (%d)", + 'gainsDesc' => "After killing this NPC you will gain: ", 'repWith' => "reputation with", 'stopsAt' => "stops at %s", 'vehicle' => "Vehicle", 'stats' => "Stats", - 'melee' => "Melee", - 'ranged' => "Ranged", - 'armor' => "Armor", - 'resistances' => "Resistances", + 'melee' => "Melee: ", + 'ranged' => "Ranged: ", + 'armor' => "Armor: ", + 'resistances' => "Resistances: ", 'foundIn' => "This NPC can be found in", 'tameable' => "Tameable (%s)", - 'spirit' => "[tooltip name=spirit]Only visible to dead players[/tooltip][span class=tip tooltip=spirit]Spirit[/span]", 'waypoint' => "Waypoint", 'wait' => "Wait", 'respawnIn' => "Respawn in: %s", - 'despawnAfter' => "Spawned by Script
Despawn after: %s", + 'despawnAfter' => "Spawned by Script
Despawn after: %s", 'rank' => [0 => "Normal", 1 => "Elite", 4 => "Rare", 2 => "Rare Elite", 3 => "Boss"], 'textRanges' => [null, "sent to area", "sent to zone", "sent to map", "sent to world"], 'textTypes' => [null, "yells", "says", "whispers"], + '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"] @@ -1102,6 +1104,33 @@ $lang = array( NPC_FLAG_GUILD_BANK => 'Guild Bank', NPC_FLAG_SPELLCLICK => 'Spellclick', NPC_FLAG_MAILBOX => 'Mailbox' + ), + 'extraFlags' => array( + CREATURE_FLAG_EXTRA_INSTANCE_BIND => 'Binds attacker to instance on death', + CREATURE_FLAG_EXTRA_CIVILIAN => "[tooltip name=civilian]- does not aggro\n- death costs Honor[/tooltip][span class=tip tooltip=civilian]Civilian[/span]", + CREATURE_FLAG_EXTRA_NO_PARRY => 'Cannot use [spell=3127]', + CREATURE_FLAG_EXTRA_NO_PARRY_HASTEN => 'Does not gain Parry Haste', + CREATURE_FLAG_EXTRA_NO_BLOCK => 'Cannot use [spell=107]', + CREATURE_FLAG_EXTRA_NO_CRUSHING_BLOWS => 'Cannot deal Crushing Blows', + CREATURE_FLAG_EXTRA_NO_XP => 'Rewards no experience', + CREATURE_FLAG_EXTRA_TRIGGER => 'Trigger Creature', + CREATURE_FLAG_EXTRA_NO_TAUNT => 'Immune to Taunt', + // CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE => '', // ?? + CREATURE_FLAG_EXTRA_GHOST_VISIBILITY => '[tooltip name=spirit]Only visible to dead players[/tooltip][span class=tip tooltip=spirit]Spirit[/span]', + CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK => 'Uses [spell=674]', + CREATURE_FLAG_EXTRA_NO_SELL_VENDOR => 'Vendor does not buy from player', + CREATURE_FLAG_EXTRA_IGNORE_COMBAT => 'Does not enter combat', + CREATURE_FLAG_EXTRA_WORLDEVENT => 'Related to World Event', + CREATURE_FLAG_EXTRA_GUARD => "[tooltip name=guard]- engages PvP attackers\n- ignores enemy stealth, invisibility and Feign Death[/tooltip][span class=tip tooltip=guard]Guard[/span]", + CREATURE_FLAG_EXTRA_IGNORE_FEIGN_DEATH => 'Ignores [spell=5384]', + CREATURE_FLAG_EXTRA_NO_CRIT => 'Cannot deal critical hits', + CREATURE_FLAG_EXTRA_NO_SKILL_GAINS => 'Attacker does not gain weapon skill', + CREATURE_FLAG_EXTRA_OBEYS_TAUNT_DIMINISHING_RETURNS => 'Taunt has diminishing returns', + CREATURE_FLAG_EXTRA_ALL_DIMINISH => 'Is subject to diminishing returns', + CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ => 'Attacking players are always eligible for loot', + // CREATURE_FLAG_EXTRA_DUNGEON_BOSS => '', // set during runtime + CREATURE_FLAG_EXTRA_IGNORE_PATHFINDING => 'Ignores pathfinding', + CREATURE_FLAG_EXTRA_IMMUNITY_KNOCKBACK => 'Immune to knockback' ) ), 'event' => array( diff --git a/localization/locale_eses.php b/localization/locale_eses.php index 1b42e770..65e22870 100644 --- a/localization/locale_eses.php +++ b/localization/locale_eses.php @@ -1040,35 +1040,37 @@ $lang = array( ), 'npc' => array( 'notFound' => "Este PNJ no existe.", - 'classification'=> "Clasificación", - 'petFamily' => "Familia de mascota", - 'react' => "Reacción", - 'worth' => "Valor", + 'classification'=> "Clasificación: %s", + 'petFamily' => "Familia de mascota: ", + 'react' => "Reacción: %s", + 'worth' => "Valor: %s", 'unkPosition' => "No se conoce la ubicación de este PNJ.", 'difficultyPH' => 'Este PNJ es un marcador de posición para un modo diferente de %2$s.', 'seat' => "Asiento", 'accessory' => "Accesorio", 'accessoryFor' => "Esta criatura es un accesorio para vehículo", - 'quotes' => "Citas", - 'gainsDesc' => "Tras acabar con este PNJ ganarás", + 'quotes' => "Citas (%d)", + 'gainsDesc' => "Tras acabar con este PNJ ganarás: ", 'repWith' => "reputación con", 'stopsAt' => "se detiene en %s", 'vehicle' => "Vehículo", 'stats' => "Estadísticas", - 'melee' => "Cuerpo a cuerpo", - 'ranged' => "Ataque a distancia", - 'armor' => "Armadura", - 'resistances' => "Resistencias", + 'melee' => "Cuerpo a cuerpo: ", + 'ranged' => "Ataque a distancia: ", + 'armor' => "Armadura: ", + 'resistances' => "Resistencias: ", 'foundIn' => "Este PNJ se puede encontrar en", 'tameable' => "Domesticable (%s)", - 'spirit' => "[tooltip name=spirit]Solo visible para jugadores muertos[/tooltip][span class=tip tooltip=spirit]Espíritu[/span]", 'waypoint' => "punto de recorrido", 'wait' => "Tiempo de espera", 'respawnIn' => "Reingreso en: %s", - 'despawnAfter' => "Generado por script
Desaparece después: %s", + 'despawnAfter' => "Generado por script
Desaparece después: %s", 'rank' => [0 => "Normal", 1 => "Élite", 4 => "Raro", 2 => "Élite raro", 3 => "Jefe"], 'textRanges' => [null, "Mandar al área", "Mandar a zona", "Mandar al mapa", "Mandar al mundo"], 'textTypes' => [null, "grita", "dice", "susurra"], + '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"] @@ -1102,6 +1104,33 @@ $lang = array( NPC_FLAG_GUILD_BANK => 'Banco de hermandad', NPC_FLAG_SPELLCLICK => 'Hechizoclic', NPC_FLAG_MAILBOX => 'Buzón' + ), + 'extraFlags' => array( + CREATURE_FLAG_EXTRA_INSTANCE_BIND => 'Vincula al atacante a la instancia al morir', + CREATURE_FLAG_EXTRA_CIVILIAN => "[tooltip name=civilian]- no agrede\n- su muerte cuesta Honor[/tooltip][span class=tip tooltip=civilian]Civil[/span]", + CREATURE_FLAG_EXTRA_NO_PARRY => 'No puede usar [spell=3127]', + CREATURE_FLAG_EXTRA_NO_PARRY_HASTEN => 'No tiene Parry Haste', + CREATURE_FLAG_EXTRA_NO_BLOCK => 'No puede usar [spell=107]', + CREATURE_FLAG_EXTRA_NO_CRUSHING_BLOWS => 'No puede infligir golpes aplastantes', + CREATURE_FLAG_EXTRA_NO_XP => 'No otorga experiencia', + CREATURE_FLAG_EXTRA_TRIGGER => 'Criatura disparadora', + CREATURE_FLAG_EXTRA_NO_TAUNT => 'Inmune a provocación', + // CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE => '', // ?? + CREATURE_FLAG_EXTRA_GHOST_VISIBILITY => '[tooltip name=spirit]Solo visible para jugadores muertos[/tooltip][span class=tip tooltip=spirit]Espíritu[/span]', + CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK => 'Usa [spell=674]', + CREATURE_FLAG_EXTRA_NO_SELL_VENDOR => 'El vendedor no compra al jugador', + CREATURE_FLAG_EXTRA_IGNORE_COMBAT => 'No entra en combate', + CREATURE_FLAG_EXTRA_WORLDEVENT => 'Relacionado con Evento Mundial', + CREATURE_FLAG_EXTRA_GUARD => "[tooltip name=guard]- ataca a jugadores PvP\n- ignora sigilo, invisibilidad y Fingir Muerte enemigos[/tooltip][span class=tip tooltip=guard]Guardia[/span]", + CREATURE_FLAG_EXTRA_IGNORE_FEIGN_DEATH => 'Ignora [spell=5384]', + CREATURE_FLAG_EXTRA_NO_CRIT => 'No puede infligir golpes críticos', + CREATURE_FLAG_EXTRA_NO_SKILL_GAINS => 'El atacante no gana habilidad de arma', + CREATURE_FLAG_EXTRA_OBEYS_TAUNT_DIMINISHING_RETURNS => 'Provocación tiene rendimientos decrecientes', + CREATURE_FLAG_EXTRA_ALL_DIMINISH => 'Está sujeto a rendimientos decrecientes', + CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ => 'Los jugadores atacantes siempre son elegibles para botín', + // CREATURE_FLAG_EXTRA_DUNGEON_BOSS => '', // set during runtime + CREATURE_FLAG_EXTRA_IGNORE_PATHFINDING => 'Ignora búsqueda de caminos', + CREATURE_FLAG_EXTRA_IMMUNITY_KNOCKBACK => 'Inmune a retroceso' ) ), 'event' => array( diff --git a/localization/locale_frfr.php b/localization/locale_frfr.php index 49b07e3b..d0ad5a56 100644 --- a/localization/locale_frfr.php +++ b/localization/locale_frfr.php @@ -1040,35 +1040,37 @@ $lang = array( ), 'npc' => array( 'notFound' => "Ce PNJ n'existe pas.", - 'classification'=> "Classification", - 'petFamily' => "Familier", - 'react' => "Réaction", - 'worth' => "Vaut", + 'classification'=> "Classification : %s", + 'petFamily' => "Familier : ", + 'react' => "Réaction : %s", + 'worth' => "Vaut : %s", 'unkPosition' => "L'emplacement de ce PNJ est inconnu.", 'difficultyPH' => 'Ce PNJ est un espace réservé pour un autre mode de difficulté %2$s.', 'seat' => "Siège", 'accessory' => "Passager", 'accessoryFor' => "Ce PNJ est un passager pour un véhicule.", - 'quotes' => "Citations", - 'gainsDesc' => "Après avoir tué ce PNJ vous allez obtenir", + 'quotes' => "Citations (%d)", + 'gainsDesc' => "Après avoir tué ce PNJ vous allez obtenir : ", 'repWith' => "points de réputation avec", 'stopsAt' => "arrête à %s", 'vehicle' => "Véhicule", 'stats' => "Statistiques", - 'melee' => "de mêlée", - 'ranged' => "à distance", - 'armor' => "Armure", - 'resistances' => "Résistances", + 'melee' => "de mêlée : ", + 'ranged' => "à distance : ", + 'armor' => "Armure : ", + 'resistances' => "Résistances : ", 'foundIn' => "Ce PNJ se trouve dans", 'tameable' => "Domptable (%s)", - 'spirit' => "[tooltip name=spirit][Only visible to dead players][/tooltip][span class=tip tooltip=spirit][Spirit][/span]", 'waypoint' => "Point de route", 'wait' => "Période d'attente", 'respawnIn' => "Rentrée en : %s", - 'despawnAfter' => "[Spawned by Script
Despawn after] : %s", + 'despawnAfter' => "[Spawned by Script
Despawn after] : %s", 'rank' => [0 => "Standard", 1 => "Élite", 4 => "Rare", 2 => "Élite rare", 3 =>"Boss"], 'textRanges' => [null, "[sent to area]", "[sent to zone]", "[sent to map]", "[sent to world]"], 'textTypes' => [null, "crie", "dit", "chuchote"], + '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"] @@ -1102,6 +1104,33 @@ $lang = array( NPC_FLAG_GUILD_BANK => 'Guild Bank', NPC_FLAG_SPELLCLICK => 'Spellclick', NPC_FLAG_MAILBOX => 'Mailbox' + ), + 'extraFlags' => array( + CREATURE_FLAG_EXTRA_INSTANCE_BIND => 'Binds attacker to instance on death', + CREATURE_FLAG_EXTRA_CIVILIAN => "[tooltip name=civilian]- does not aggro\n- death costs Honor[/tooltip][span class=tip tooltip=civilian]Civilian[/span]", + CREATURE_FLAG_EXTRA_NO_PARRY => 'Cannot use [spell=3127]', + CREATURE_FLAG_EXTRA_NO_PARRY_HASTEN => 'Does not gain Parry Haste', + CREATURE_FLAG_EXTRA_NO_BLOCK => 'Cannot use [spell=107]', + CREATURE_FLAG_EXTRA_NO_CRUSHING_BLOWS => 'Cannot deal Crushing Blows', + CREATURE_FLAG_EXTRA_NO_XP => 'Rewards no experience', + CREATURE_FLAG_EXTRA_TRIGGER => 'Trigger Creature', + CREATURE_FLAG_EXTRA_NO_TAUNT => 'Immune to Taunt', + // CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE => '', // ?? + CREATURE_FLAG_EXTRA_GHOST_VISIBILITY => '[tooltip name=spirit]Only visible to dead players[/tooltip][span class=tip tooltip=spirit]Spirit[/span]', + CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK => 'Uses [spell=674]', + CREATURE_FLAG_EXTRA_NO_SELL_VENDOR => 'Vendor does not buy from player', + CREATURE_FLAG_EXTRA_IGNORE_COMBAT => 'Does not enter combat', + CREATURE_FLAG_EXTRA_WORLDEVENT => 'Related to World Event', + CREATURE_FLAG_EXTRA_GUARD => "[tooltip name=guard]- engages PvP attackers\n- ignores enemy stealth, invisibility and Feign Death[/tooltip][span class=tip tooltip=guard]Guard[/span]", + CREATURE_FLAG_EXTRA_IGNORE_FEIGN_DEATH => 'Ignores [spell=5384]', + CREATURE_FLAG_EXTRA_NO_CRIT => 'Cannot deal critical hits', + CREATURE_FLAG_EXTRA_NO_SKILL_GAINS => 'Attacker does not gain weapon skill', + CREATURE_FLAG_EXTRA_OBEYS_TAUNT_DIMINISHING_RETURNS => 'Taunt has diminishing returns', + CREATURE_FLAG_EXTRA_ALL_DIMINISH => 'Is subject to diminishing returns', + CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ => 'Attacking players are always eligible for loot', + // CREATURE_FLAG_EXTRA_DUNGEON_BOSS => '', // set during runtime + CREATURE_FLAG_EXTRA_IGNORE_PATHFINDING => 'Ignores pathfinding', + CREATURE_FLAG_EXTRA_IMMUNITY_KNOCKBACK => 'Immune to knockback' ) ), 'event' => array( diff --git a/localization/locale_ruru.php b/localization/locale_ruru.php index a9a9f7e8..6034134e 100644 --- a/localization/locale_ruru.php +++ b/localization/locale_ruru.php @@ -1040,35 +1040,37 @@ $lang = array( ), 'npc' => array( 'notFound' => "Такой НИП не существует.", - 'classification'=> "Классификация", - 'petFamily' => "Семейство питомца", - 'react' => "Реакция", - 'worth' => "Деньги", + 'classification'=> "Классификация: %s", + 'petFamily' => "Семейство питомца: ", + 'react' => "Реакция: %s", + 'worth' => "Деньги: %s", 'unkPosition' => "Местоположение этого НИП неизвестно.", 'difficultyPH' => '[Этот НИП является прототипом для другого режима %2$s.]', 'seat' => "[Seat]", 'accessory' => "[Accessory]", 'accessoryFor' => "[This creature is an accessory for vehicle]", - 'quotes' => "Цитаты", - 'gainsDesc' => "В награду за убийство этого НИПа вы получите", + 'quotes' => "Цитаты (%d)", + 'gainsDesc' => "В награду за убийство этого НИПа вы получите: ", 'repWith' => "репутации с", 'stopsAt' => 'останавливается на уровне "%s"', 'vehicle' => "Автомобиль", 'stats' => "Характеристики", - 'melee' => "Ближнего боя", - 'ranged' => "Дальнего боя", - 'armor' => "Броня", - 'resistances' => "Сопротивление", + 'melee' => "Ближнего боя: ", + 'ranged' => "Дальнего боя: ", + 'armor' => "Броня: ", + 'resistances' => "Сопротивление: ", 'foundIn' => "Этот объект может быть найден в следующих зонах:", 'tameable' => "Можно приручить (%s)", - 'spirit' => "[tooltip name=spirit][Only visible to dead players][/tooltip][span class=tip tooltip=spirit][Spirit][/span]", 'waypoint' => "Путевой точки", 'wait' => "Период ожидания", 'respawnIn' => "Reentry in: %s", - 'despawnAfter' => "[Spawned by Script
Despawn after]: %s", + 'despawnAfter' => "[Spawned by Script
Despawn after]: %s", 'rank' => [0 => "Обычный", 1 => "Элитный", 4 => "Редкий", 2 => "Редкий элитный", 3 =>"Босс"], 'textRanges' => [null, "[sent to area]", "[sent to zone]", "[sent to map]", "[sent to world]"], 'textTypes' => [null, "кричит", "говорит", "шепчет"], + 'mechanicimmune'=> '[Not affected by mechanic]: %s', + '_extraFlags' => '[Extra Flags]: ', + 'versions' => '[Difficulty Versions]: ', 'modes' => array( 1 => ["Обычный", "Героический"], 2 => ["10 нормал.", "25 нормал.", "10 героич.", "25 героич."] @@ -1102,6 +1104,33 @@ $lang = array( NPC_FLAG_GUILD_BANK => 'Guild Bank', NPC_FLAG_SPELLCLICK => 'Spellclick', NPC_FLAG_MAILBOX => 'Mailbox' + ), + 'extraFlags' => array( + CREATURE_FLAG_EXTRA_INSTANCE_BIND => 'Binds attacker to instance on death', + CREATURE_FLAG_EXTRA_CIVILIAN => "[tooltip name=civilian]- does not aggro\n- death costs Honor[/tooltip][span class=tip tooltip=civilian]Civilian[/span]", + CREATURE_FLAG_EXTRA_NO_PARRY => 'Cannot use [spell=3127]', + CREATURE_FLAG_EXTRA_NO_PARRY_HASTEN => 'Does not gain Parry Haste', + CREATURE_FLAG_EXTRA_NO_BLOCK => 'Cannot use [spell=107]', + CREATURE_FLAG_EXTRA_NO_CRUSHING_BLOWS => 'Cannot deal Crushing Blows', + CREATURE_FLAG_EXTRA_NO_XP => 'Rewards no experience', + CREATURE_FLAG_EXTRA_TRIGGER => 'Trigger Creature', + CREATURE_FLAG_EXTRA_NO_TAUNT => 'Immune to Taunt', + // CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE => '', // ?? + CREATURE_FLAG_EXTRA_GHOST_VISIBILITY => '[tooltip name=spirit]Only visible to dead players[/tooltip][span class=tip tooltip=spirit]Spirit[/span]', + CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK => 'Uses [spell=674]', + CREATURE_FLAG_EXTRA_NO_SELL_VENDOR => 'Vendor does not buy from player', + CREATURE_FLAG_EXTRA_IGNORE_COMBAT => 'Does not enter combat', + CREATURE_FLAG_EXTRA_WORLDEVENT => 'Related to World Event', + CREATURE_FLAG_EXTRA_GUARD => "[tooltip name=guard]- engages PvP attackers\n- ignores enemy stealth, invisibility and Feign Death[/tooltip][span class=tip tooltip=guard]Guard[/span]", + CREATURE_FLAG_EXTRA_IGNORE_FEIGN_DEATH => 'Ignores [spell=5384]', + CREATURE_FLAG_EXTRA_NO_CRIT => 'Cannot deal critical hits', + CREATURE_FLAG_EXTRA_NO_SKILL_GAINS => 'Attacker does not gain weapon skill', + CREATURE_FLAG_EXTRA_OBEYS_TAUNT_DIMINISHING_RETURNS => 'Taunt has diminishing returns', + CREATURE_FLAG_EXTRA_ALL_DIMINISH => 'Is subject to diminishing returns', + CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ => 'Attacking players are always eligible for loot', + // CREATURE_FLAG_EXTRA_DUNGEON_BOSS => '', // set during runtime + CREATURE_FLAG_EXTRA_IGNORE_PATHFINDING => 'Ignores pathfinding', + CREATURE_FLAG_EXTRA_IMMUNITY_KNOCKBACK => 'Immune to knockback' ) ), 'event' => array( diff --git a/localization/locale_zhcn.php b/localization/locale_zhcn.php index 514b70c2..876b8c90 100644 --- a/localization/locale_zhcn.php +++ b/localization/locale_zhcn.php @@ -1039,35 +1039,37 @@ $lang = array( ), 'npc' => array( 'notFound' => "这个NPC不存在。", - 'classification'=> "分类", - 'petFamily' => "宠物家族", - 'react' => "反应", - 'worth' => "价值", + 'classification'=> "分类:%s", + 'petFamily' => "宠物家族:", + 'react' => "反应:%s", + 'worth' => "价值:%s", 'unkPosition' => "这个NPC的位置未知。", 'difficultyPH' => '这个NPC是不同模式下的占位符,是 %2$s.', 'seat' => "Seat", 'accessory' => "附件", 'accessoryFor' => "这个NPC是载具的附件", - 'quotes' => "引用", - 'gainsDesc' => "杀死这个NPC后你将得到", + 'quotes' => "引用(%d)", + 'gainsDesc' => "杀死这个NPC后你将得到:", 'repWith' => "点声望点数在", 'stopsAt' => "在%s停止", 'vehicle' => "载具", 'stats' => "状态", - 'melee' => "近战", - 'ranged' => "远程", - 'armor' => "护甲", - 'resistances' => "韧性", + 'melee' => "近战:", + 'ranged' => "远程:", + 'armor' => "护甲:", + 'resistances' => "韧性:", 'foundIn' => "这个NPC能在以下地区找到:", 'tameable' => "可驯服的(%s)", - 'spirit' => "[tooltip name=spirit][Only visible to dead players][/tooltip][span class=tip tooltip=spirit][Spirit][/span]", 'waypoint' => "路径点", 'wait' => "等待", 'respawnIn' => "重生:%s", - 'despawnAfter' => "[Spawned by Script
Despawn after]:%s", + 'despawnAfter' => "[Spawned by Script
Despawn after]:%s", 'rank' => [0 => "普通", 1 => "稀有", 4 => "精英", 2 => "稀有精英", 3 => "首领"], 'textRanges' => [null, "发送到地区", "发送到区域", "发送到地图", "发送到世界"], 'textTypes' => [null, "喊道", "说", "悄悄地说"], + 'mechanicimmune'=> '[Not affected by mechanic]:%s', + '_extraFlags' => '[Extra Flags]:', + 'versions' => '[Difficulty Versions]:', 'modes' => array( 1 => ["普通", "英雄"], 2 => ["10人普通", "25人普通", "10人英雄", "25人英雄"] @@ -1101,6 +1103,33 @@ $lang = array( NPC_FLAG_GUILD_BANK => '公会银行', NPC_FLAG_SPELLCLICK => 'Spellclick', NPC_FLAG_MAILBOX => '邮箱' + ), + 'extraFlags' => array( + CREATURE_FLAG_EXTRA_INSTANCE_BIND => 'Binds attacker to instance on death', + CREATURE_FLAG_EXTRA_CIVILIAN => "[tooltip name=civilian]- does not aggro\n- death costs Honor[/tooltip][span class=tip tooltip=civilian]Civilian[/span]", + CREATURE_FLAG_EXTRA_NO_PARRY => 'Cannot use [spell=3127]', + CREATURE_FLAG_EXTRA_NO_PARRY_HASTEN => 'Does not gain Parry Haste', + CREATURE_FLAG_EXTRA_NO_BLOCK => 'Cannot use [spell=107]', + CREATURE_FLAG_EXTRA_NO_CRUSHING_BLOWS => 'Cannot deal Crushing Blows', + CREATURE_FLAG_EXTRA_NO_XP => 'Rewards no experience', + CREATURE_FLAG_EXTRA_TRIGGER => 'Trigger Creature', + CREATURE_FLAG_EXTRA_NO_TAUNT => 'Immune to Taunt', + // CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE => '', // ?? + CREATURE_FLAG_EXTRA_GHOST_VISIBILITY => '[tooltip name=spirit]Only visible to dead players[/tooltip][span class=tip tooltip=spirit]Spirit[/span]', + CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK => 'Uses [spell=674]', + CREATURE_FLAG_EXTRA_NO_SELL_VENDOR => 'Vendor does not buy from player', + CREATURE_FLAG_EXTRA_IGNORE_COMBAT => 'Does not enter combat', + CREATURE_FLAG_EXTRA_WORLDEVENT => 'Related to World Event', + CREATURE_FLAG_EXTRA_GUARD => "[tooltip name=guard]- engages PvP attackers\n- ignores enemy stealth, invisibility and Feign Death[/tooltip][span class=tip tooltip=guard]Guard[/span]", + CREATURE_FLAG_EXTRA_IGNORE_FEIGN_DEATH => 'Ignores [spell=5384]', + CREATURE_FLAG_EXTRA_NO_CRIT => 'Cannot deal critical hits', + CREATURE_FLAG_EXTRA_NO_SKILL_GAINS => 'Attacker does not gain weapon skill', + CREATURE_FLAG_EXTRA_OBEYS_TAUNT_DIMINISHING_RETURNS => 'Taunt has diminishing returns', + CREATURE_FLAG_EXTRA_ALL_DIMINISH => 'Is subject to diminishing returns', + CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ => 'Attacking players are always eligible for loot', + // CREATURE_FLAG_EXTRA_DUNGEON_BOSS => '', // set during runtime + CREATURE_FLAG_EXTRA_IGNORE_PATHFINDING => 'Ignores pathfinding', + CREATURE_FLAG_EXTRA_IMMUNITY_KNOCKBACK => 'Immune to knockback' ) ), 'event' => array( diff --git a/pages/npcs.php b/pages/npcs.php deleted file mode 100644 index cd5e9bd8..00000000 --- a/pages/npcs.php +++ /dev/null @@ -1,115 +0,0 @@ - ['filter' => FILTER_UNSAFE_RAW]]; - - public function __construct($pageCall, $pageParam) - { - $this->getCategoryFromUrl($pageParam); - - parent::__construct($pageCall, $pageParam); - - $this->filterObj = new CreatureListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]); - - $this->name = Util::ucFirst(Lang::game('npcs')); - $this->subCat = $pageParam ? '='.$pageParam : ''; - } - - protected function generateContent() - { - $this->addScript([SC_JS_FILE, '?data=zones']); - - $conditions = []; - - if (!User::isInGroup(U_GROUP_EMPLOYEE)) - $conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0]; - - if ($this->category) - { - $conditions[] = ['type', $this->category[0]]; - $this->petFamPanel = $this->category[0] == 1; - } - - $this->filterObj->evalCriteria(); - - if ($_ = $this->filterObj->getConditions()) - $conditions[] = $_; - - // beast subtypes are selected via filter - $npcs = new CreatureList($conditions, ['extraOpts' => $this->filterObj->extraOpts, 'calcTotal' => true]); - - $rCols = $this->filterObj->fiReputationCols; - - $tabData = ['data' => array_values($npcs->getListviewData($rCols ? NPCINFO_REP : 0x0))]; - - if ($rCols) // never use pretty-print - $tabData['extraCols'] = '$fi_getReputationCols('.Util::toJSON($rCols, JSON_NUMERIC_CHECK | JSON_UNESCAPED_UNICODE).')'; - else if ($this->filterObj->fiExtraCols) - $tabData['extraCols'] = '$fi_getExtraCols(fi_extraCols, 0, 0)'; - - if ($this->category) - $tabData['hiddenCols'] = ['type']; - - // create note if search limit was exceeded - if ($npcs->getMatches() > Cfg::get('SQL_LIMIT_DEFAULT')) - { - $tabData['note'] = sprintf(Util::$tryFilteringString, 'LANG.lvnote_npcsfound', $npcs->getMatches(), Cfg::get('SQL_LIMIT_DEFAULT')); - $tabData['_truncated'] = 1; - } - - if ($this->filterObj->error) - $tabData['_errors'] = 1; - - $this->lvTabs[] = [CreatureList::$brickFile, $tabData]; - } - - protected function postCache() - { - // sort for dropdown-menus - Lang::sort('game', 'fa'); - } - - protected function generateTitle() - { - array_unshift($this->title, $this->name); - if ($this->category) - array_unshift($this->title, Lang::npc('cat', $this->category[0])); - - $form = $this->filterObj->values; - if (count($form['fa']) == 1) - array_unshift($this->title, Lang::game('fa', $form['fa'][0])); - } - - protected function generatePath() - { - if ($this->category) - $this->path[] = $this->category[0]; - - $form = $this->filterObj->values; - if (count($form['fa'])) - $this->path[] = $form['fa'][0]; - } -} - -?> diff --git a/template/bricks/mapper.tpl.php b/template/bricks/mapper.tpl.php index 50e89719..ed0ba3be 100644 --- a/template/bricks/mapper.tpl.php +++ b/template/bricks/mapper.tpl.php @@ -1,30 +1,24 @@ - - map) && empty($this->map)): - echo Lang::zone('noMap'); -elseif (!empty($this->map['data'])): - if ($this->type == Type::QUEST) : - echo "
\n"; - elseif ($this->map['mapperData']): - echo '
'; + namespace Aowow\Template; - echo $this->map['foundIn']; + use \Aowow\Lang; - echo ' '; - - echo Lang::concat($this->map['mapperData'], true, function ($areaData, $areaId) { - return ''.$this->map['extra'][$areaId].' ('.array_sum(array_column($areaData, 'count')).')'; +if ([$mapper, $mapperData, $som, $foundIn] = $this->map): + if ($foundIn): + echo '
'.$foundIn[0].' '; + echo Lang::concat($mapperData, true, function ($areaData, $areaId) use ($foundIn) { + return ''.$foundIn[$areaId].' ('.array_sum(array_column($areaData, 'count')).')'; }); - echo ".
\n"; + else: + echo "
\n"; endif; - if (!empty($this->map['data']['zone']) && $this->map['data']['zone'] < 0): + if (isset($mapper['zone']) && $mapper['zone'] < 0): ?>
map['som'])): + if ($som): ?>
map['data'])):
-map['som'])): + elseif ($mapper): + if ($som): ?>
@@ -47,48 +39,45 @@ elseif (!empty($this->map['data'])):
- - diff --git a/template/pages/npc.tpl.php b/template/pages/npc.tpl.php index 7cb15e23..a6dfeac1 100644 --- a/template/pages/npc.tpl.php +++ b/template/pages/npc.tpl.php @@ -1,7 +1,10 @@ - +brick('header'); ?> + use \Aowow\Lang; + $this->brick('header'); +?>
@@ -17,49 +20,43 @@
brick('redButtons'); ?> -

name.($this->subname ? ' <'.$this->subname.'>' : null); ?>

+

h1.($this->subname ? ' <'.$this->subname.'>' : ''); ?>

brick('article'); + $this->brick('markup', ['markup' => $this->article]); if ($this->accessory): echo '
'.Lang::npc('accessoryFor').' '; - echo Lang::concat($this->accessory, true, function ($v, $k) { return ''.$v[1].''; }); + echo Lang::concat($this->accessory, true, fn ($v) => ''.$v[1].''); echo ".
\n"; endif; if ($this->placeholder): - echo '
'.Lang::npc('difficultyPH', $this->placeholder)."
\n"; ?> +
placeholder);?>
map)): +elseif ($this->map): $this->brick('mapper'); else: echo ' '.Lang::npc('unkPosition')."\n"; endif; -if ($this->quotes[0]): +if ([$quoteGroups, $count] = $this->quotes): ?> -

quotes[1]; ?>)

+

'; + foreach ($data as [$id, $qty, $name, $cap]): + echo '
  • '.($qty[1] ?: $qty[0]).' '.Lang::npc('repWith') . + ' '.$name.''.($cap && $qty[0] > 0 ? ' ('.Lang::npc('stopsAt', [$cap]).')' : '').'
  • '; endforeach; echo ''; @@ -100,25 +92,14 @@ if ($this->reputation): endforeach; endif; -if (isset($this->smartAI)): -?> -
    - +$this->brick('markup', ['markup' => $this->smartAI]); -
    -

    brick('lvTabs', ['relTabs' => true]); +$this->brick('lvTabs'); $this->brick('contribute'); ?> diff --git a/template/pages/npcs.tpl.php b/template/pages/npcs.tpl.php index cb732ed6..1ba1cd19 100644 --- a/template/pages/npcs.tpl.php +++ b/template/pages/npcs.tpl.php @@ -1,10 +1,11 @@ - - brick('header'); -$f = $this->filterObj->values // shorthand -?> + namespace Aowow\Template; + use \Aowow\Lang; + +$this->brick('header'); +$f = $this->filter->values; // shorthand +?>
    @@ -12,67 +13,62 @@ $f = $this->filterObj->values // shorthand brick('announcement'); -$this->brick('pageTemplate', ['fiQuery' => $this->filterObj->query, 'fiMenuItem' => [4]]); +$this->brick('pageTemplate', ['fiQuery' => $this->filter->query, 'fiMenuItem' => [4]]); ?> - -
    +
    +
    +brick('headIcons'); + +$this->brick('redButtons'); +?> +

    h1; ?>

    +
    -
    +
    petFamPanel): ?>
    -
    +
    - + - +
    ucFirst(Lang::main('name')).Lang::main('colon'); ?> - - + +
     />  /> />  />
     /> - /> /> - /> - - +
             - > - - - + + +
    @@ -84,7 +80,7 @@ endforeach;
    - /> /> + /> />
    @@ -98,7 +94,7 @@ endforeach;
    -brick('filter'); ?> +renderFilter(12); ?> brick('lvTabs'); ?>