srcType = $srcType; $this->entry = $entry; $this->miscData = $miscData; $raw = DB::World()->select('SELECT id, link, event_type, event_phase_mask, event_chance, event_flags, event_param1, event_param2, event_param3, event_param4, action_type, action_param1, action_param2, action_param3, action_param4, action_param5, action_param6, target_type, target_param1, target_param2, target_param3, target_x, target_y, target_z, target_o FROM smart_scripts WHERE entryorguid = ?d AND source_type = ?d ORDER BY id ASC', $this->entry, $this->srcType); foreach ($raw as $r) { $this->rawData[$r['id']] = array( 'id' => $r['id'], 'link' => $r['link'], 'event' => array( 'type' => $r['event_type'], 'phases' => Util::mask2bits($r['event_phase_mask'], 1) ?: [0], 'chance' => $r['event_chance'], 'flags' => $r['event_flags'], 'param' => [$r['event_param1'], $r['event_param2'], $r['event_param3'], $r['event_param4'], 0] ), 'action' => array( 'type' => $r['action_type'], 'param' => [$r['action_param1'], $r['action_param2'], $r['action_param3'], $r['action_param4'], $r['action_param5'], $r['action_param6']] ), 'target' => array( 'type' => $r['target_type'], 'param' => [$r['target_param1'], $r['target_param2'], $r['target_param3']], 'pos' => [$r['target_x'], $r['target_y'], $r['target_z'], $r['target_o']] ) ); } } public function prepare() : bool { if (!$this->rawData) return false; if ($this->result) return true; $hidePhase = $hideChance = true; foreach ($this->iterate() as $_) { $this->rowKey = Util::createHash(8); if ($ts = $this->getTalkSource()) $this->getQuotes($ts); [$evtBody, $evtFooter] = $this->event(); [$actBody, $actFooter] = $this->action(); if ($ef = $this->eventFlags()) { if ($evtFooter) $evtFooter = $ef.', '.$evtFooter; else $evtFooter = $ef; } if ($this->itr['event']['phases'] != [0]) $hidePhase = false; if ($this->itr['event']['chance'] != 100) $hideChance = false; $this->result[] = array( $this->itr['id'], implode(', ', $this->itr['event']['phases']), $evtBody.($evtFooter ? '[div float=right margin=0px][i][small class=q0]'.$evtFooter.'[/small][/i][/div]' : null), $this->itr['event']['chance'].'%', $actBody.($actFooter ? '[div float=right margin=0px clear=both][i][small class=q0]'.$actFooter.'[/small][/i][/div]' : null) ); } $th = array( '#' => 16, 'Phase' => 32, 'Event' => 350, 'Chance' => 24, 'Action' => 0 ); if ($hidePhase) { unset($th['Phase']); foreach ($this->result as &$r) unset($r[1]); } unset($r); if ($hideChance) { unset($th['Chance']); foreach ($this->result as &$r) unset($r[3]); } unset($r); $tbl = '[tr]'; foreach ($th as $n => $w) $tbl .= '[td header '.($w ? 'width='.$w.'px' : null).']'.$n.'[/td]'; $tbl .= '[/tr]'; foreach ($this->result as $r) $tbl .= '[tr][td]'.implode('[/td][td]', $r).'[/td][/tr]'; if ($this->srcType == SAI_SRC_TYPE_ACTIONLIST) $this->tabs[$this->entry] = $tbl; else $this->tabs[0] = $tbl; return true; } public function getMarkdown() : string { # id | event (footer phase) | chance | action + target if (!$this->rawData) return ''; $return = '[style]#text-generic .grid { clear:left; } #text-generic .tabbed-contents { padding:0px; clear:left; }[/style][pad][h3][toggler id=sai]SmartAI'.(!empty($this->miscData['title']) ? $this->miscData['title'] : null).'[/toggler][/h3][div id=sai clear=left]%s[/div]'; if (count($this->tabs) > 1) { $wrapper = '[tabs name=sai width=942px]%s[/tabs]'; $tabs = ''; foreach ($this->tabs as $guid => $data) { $buff = '[tab name=\"'.($guid ? 'ActionList #'.$guid : 'Main').'\"][table class=grid width=940px]'.$data.'[/table][/tab]'; if ($guid) $tabs .= $buff; else $tabs = $buff . $tabs; } return sprintf($return, sprintf($wrapper, $tabs)); } else return sprintf($return, '[table class=grid width=940px]'.$this->tabs[0].'[/table]'); } public function getJSGlobals() : array { return $this->jsGlobals; } public function getTabs() : array { return $this->tabs; } private function &iterate() : iterable { reset($this->rawData); foreach ($this->rawData as $k => $__) { $this->itr = &$this->rawData[$k]; yield $this->itr; } } private function numRange(string $f, int $n, bool $isTime = false) : string { if (!isset($this->itr[$f]['param'][$n]) || !isset($this->itr[$f]['param'][$n + 1])) return 0; if (empty($this->itr[$f]['param'][$n]) && empty($this->itr[$f]['param'][$n + 1])) return 0; $str = $isTime ? Util::formatTime($this->itr[$f]['param'][$n], true) : $this->itr[$f]['param'][$n]; if ($this->itr[$f]['param'][$n + 1] > $this->itr[$f]['param'][$n]) $str .= ' – '.($isTime ? Util::formatTime($this->itr[$f]['param'][$n + 1], true) : $this->itr[$f]['param'][$n + 1]); return $str; } private function getQuotes(int $creatureId) : void { if (isset($this->quotes[$creatureId])) return; $quoteSrc = DB::World()->select(' SELECT ct.CreatureID, ct.GroupID, ct.ID, ct.`Type`, ct.TextRange AS `range`, IFNULL(bct.`Language`, ct.`Language`) AS lang, IFNULL(NULLIF(bct.MaleText, ""), IFNULL(NULLIF(bct.FemaleText, ""), IFNULL(ct.`Text`, ""))) AS text_loc0, {IFNULL(NULLIF(bctl.MaleText, ""), IFNULL(NULLIF(bctl.FemaleText, ""), IFNULL(ctl.Text, ""))) AS text_loc?d,} IF(bct.SoundId > 0, bct.SoundId, ct.Sound) AS soundId FROM creature_text ct {LEFT JOIN creature_text_locale ctl ON ct.CreatureID = ctl.CreatureID AND ct.GroupID = ctl.GroupID AND ct.ID = ctl.ID AND ctl.Locale = ?} LEFT JOIN broadcast_text bct ON ct.BroadcastTextId = bct.ID {LEFT JOIN broadcast_text_locale bctl ON ct.BroadcastTextId = bctl.ID AND bctl.locale = ?} WHERE ct.CreatureID = ?d', User::$localeId ?: DBSIMPLE_SKIP, User::$localeId ? Util::$localeStrings[User::$localeId] : DBSIMPLE_SKIP, User::$localeId ? Util::$localeStrings[User::$localeId] : DBSIMPLE_SKIP, $creatureId ); foreach ($quoteSrc as $text) { $msg = Util::localizedString($text, 'text'); if (!$msg) continue; // fixup .. either set %s for emotes or dont >.< if (in_array($text['Type'], [2, 16]) && strpos($msg, '%s') === false) $msg = '%s '.$msg; // fixup: bad case-insensivity $msg = str_replace('%S', '%s', $msg); $line = array( 'range' => $text['range'], 'type' => 2, // [type: 0, 12] say: yellow-ish 'lang' => !empty($text['lang']) ? Lang::game('languages', $text['lang']) : null, 'text' => Util::parseHtmlText(htmlentities($msg), true) ); switch ($text['Type']) { case 1: // yell: case 14: $line['type'] = 1; break; // - dark red case 2: // emote: case 16: // " case 3: // boss emote: case 41: $line['type'] = 4; break; // - orange case 4: // whisper: case 15: // " case 5: // boss whisper: case 42: $line['type'] = 3; break; // - pink-ish } $this->quotes[$text['CreatureID']][$text['GroupID']][] = $line; } if (isset($this->quotes[$text['CreatureID']])) $this->quotes[$text['CreatureID']]['src'] = Util::jsEscape(CreatureList::getName($text['CreatureID'])); } private function getTalkSource() : int { if ($this->itr['action']['type'] != SAI_ACTION_TALK && $this->itr['action']['type'] != SAI_ACTION_SIMPLE_TALK) return 0; switch ($this->itr['target']['type']) { case SAI_TARGET_CREATURE_GUID: if ($id = DB::World()->selectCell('SELECT id FROM creature WHERE guid = ?d', $this->itr['target']['param'][0])) return $id; break; case SAI_TARGET_CREATURE_RANGE: case SAI_TARGET_CREATURE_DISTANCE: case SAI_TARGET_CLOSEST_CREATURE: return $this->itr['target']['param'][0]; case SAI_TARGET_SELF: case SAI_TARGET_ACTION_INVOKER: case SAI_TARGET_CLOSEST_PLAYER: case SAI_TARGET_CLOSEST_FRIENDLY: // unsure about this default: return empty($this->miscData['baseEntry']) ? $this->entry : $this->miscData['baseEntry']; } return 0; } private function eventFlags() : string { $ef = []; for ($i = 1; $i <= SAI_EVENT_FLAG_WHILE_CHARMED; $i <<= 1) if ($this->itr['event']['flags'] & $i) if ($x = Lang::smartAI('eventFlags', $i)) $ef[] = $x; return Lang::concat($ef); } private function castFlags(string $f, int $n) : string { $cf = []; for ($i = 1; $i <= SAI_CAST_FLAG_COMBAT_MOVE; $i <<= 1) if ($this->itr[$f]['param'][$n] & $i) if ($x = Lang::smartAI('castFlags', $i)) $cf[] = $x; return Lang::concat($cf); } private function npcFlags(string $f, int $n) : string { $nf = []; for ($i = 1; $i <= NPC_FLAG_MAILBOX; $i <<= 1) if ($this->itr[$f]['param'][$n] & $i) if ($x = Lang::npc('npcFlags', $i)) $nf[] = $x; return Lang::concat($nf ?: [Lang::smartAI('empty')]); } private function unitFlags(string $f, int $n) : string { $uf = []; for ($i = 1; $i <= UNIT_FLAG_UNK_31; $i <<= 1) if ($this->itr[$f]['param'][$n] & $i) if ($x = Lang::unit('flags', $i)) $uf[] = $x; return Lang::concat($uf ?: [Lang::smartAI('empty')]); } private function unitFlags2(string $f, int $n) : string { $uf = []; for ($i = 1; $i <= UNIT_FLAG2_ALLOW_CHEAT_SPELLS; $i <<= 1) if ($this->itr[$f]['param'][$n] & $i) if ($x = Lang::unit('flags2', $i)) $uf[] = $x; return Lang::concat($uf ?: [Lang::smartAI('empty')]); } private function unitFieldBytes1(int $idx, int $val) : string { if ($idx === 0) { if ($standState = Lang::unit('bytes1', $idx, $val)) return $standState; else return Lang::unit('bytes1', 'valueUNK', [$val, $idx]); } else if ($idx === 2 || $idx == 3) { $buff = []; for ($i = 1; $i <= 0x10; $i <<= 1) if ($val & $i) if ($x = Lang::unit('bytes1', $idx, $val)) $buff[] = $x; return $buff ? Lang::concat($buff) : Lang::unit('bytes1', 'valueUNK', [$val, $idx]); } else return Lang::unit('bytes1', 'idxUNK', [$idx]); } private function summonType(int $summonType) : string { if ($summonType = Lang::smartAI('summonTypes', $summonType)) return $summonType; else return Lang::smartAI('summonType', 'summonTypeUNK', [$summonType]); } private function dynFlags(string $f, int $n) : string { $df = []; for ($i = 1; $i <= UNIT_DYNFLAG_TAPPED_BY_ALL_THREAT_LIST; $i <<= 1) if ($this->itr[$f]['param'][$n] & $i) if ($x = Lang::unit('dynFlags', $i)) $df[] = $x; return Lang::concat($df ?: [Lang::smartAI('empty')]); } private function goFlags(string $f, int $n) : string { $gf = []; for ($i = 1; $i <= GO_FLAG_DESTROYED; $i <<= 1) if ($this->itr[$f]['param'][$n] & $i) if ($x = Lang::gameObject('goFlags', $i)) $gf[] = $x; return Lang::concat($gf ?: [Lang::smartAI('empty')]); } private function aiTemplate(int $aiNum) : string { if ($standState = Lang::smartAI('aiTpl', $aiNum)) return $standState; else return Lang::smartAI('aiTplUNK', [$aiNum]); } private function reactState(int $stateNum) : string { if ($reactState = Lang::smartAI('reactStates', $stateNum)) return $reactState; else return Lang::smartAI('reactStateUNK', [$stateNum]); } private function target(array $override = []) : string { $target = ''; $t = $override ?: $this->itr['target']; $getDist = function ($min, $max) { return ($min && $max) ? min($min, $max).' – '.max($min, $max) : max($min, $max); }; $tooltip = '[tooltip name=t-'.$this->rowKey.']'.Lang::smartAI('targetTT', array_merge([$t['type']], $t['param'], $t['pos'])).'[/tooltip][span class=tip tooltip=t-'.$this->rowKey.']%s[/span]'; // additional parameters $t['param'][3] = ''; switch ($t['type']) { // direct param use case SAI_TARGET_SELF: // 1 case SAI_TARGET_VICTIM: // 2 case SAI_TARGET_HOSTILE_SECOND_AGGRO: // 3 case SAI_TARGET_HOSTILE_LAST_AGGRO: // 4 case SAI_TARGET_HOSTILE_RANDOM: // 5 case SAI_TARGET_HOSTILE_RANDOM_NOT_TOP: // 6 case SAI_TARGET_ACTION_INVOKER: // 7 case SAI_TARGET_POSITION: // 8 case SAI_TARGET_STORED: // 12 case SAI_TARGET_INVOKER_PARTY: // 16 case SAI_TARGET_CLOSEST_PLAYER: // 21 case SAI_TARGET_ACTION_INVOKER_VEHICLE: // 22 case SAI_TARGET_OWNER_OR_SUMMONER: // 23 case SAI_TARGET_THREAT_LIST: // 24 case SAI_TARGET_CLOSEST_ENEMY: // 25 case SAI_TARGET_CLOSEST_FRIENDLY: // 26 case SAI_TARGET_LOOT_RECIPIENTS: // 27 case SAI_TARGET_FARTHEST: // 28 case SAI_TARGET_VEHICLE_ACCESSORY: // 29 break; // distance case SAI_TARGET_PLAYER_RANGE: // 17 $t['param'][3] = $getDist($t['param'][0], $t['param'][1]); break; case SAI_TARGET_PLAYER_DISTANCE: // 18 $t['param'][3] = $getDist(0, $t['param'][0]); break; // creature link case SAI_TARGET_CREATURE_RANGE: // 9 if ($t['param'][0]) $this->jsGlobals[TYPE_NPC][] = $t['param'][0]; $t['param'][3] = $getDist($t['param'][1], $t['param'][2]); break; case SAI_TARGET_CREATURE_GUID: // 10 if ($t['param'][3] = DB::World()->selectCell('SELECT id FROM creature WHERE guid = ?d', $t['param'][0])) $this->jsGlobals[TYPE_NPC][] = $t['param'][3]; else trigger_error('SmartAI::resloveTarget - creature with guid '.$t['param'][0].' not in DB'); break; case SAI_TARGET_CREATURE_DISTANCE: // 11 case SAI_TARGET_CLOSEST_CREATURE: // 19 $t['param'][3] = $getDist(0, $t['param'][1]); if ($t['param'][0]) $this->jsGlobals[TYPE_NPC][] = $t['param'][0]; break; // gameobject link case SAI_TARGET_GAMEOBJECT_GUID: // 14 if ($t['param'][3] = DB::World()->selectCell('SELECT id FROM gameobject WHERE guid = ?d', $t['param'][0])) $this->jsGlobals[TYPE_OBJECT][] = $t['param'][3]; else trigger_error('SmartAI::resloveTarget - gameobject with guid '.$t['param'][0].' not in DB'); break; case SAI_TARGET_GAMEOBJECT_RANGE: // 13 $t['param'][3] = $getDist($t['param'][1], $t['param'][2]); if ($t['param'][0]) $this->jsGlobals[TYPE_OBJECT][] = $t['param'][0]; break; case SAI_TARGET_GAMEOBJECT_DISTANCE: // 15 case SAI_TARGET_CLOSEST_GAMEOBJECT: // 20 $t['param'][3] = $getDist(0, $t['param'][1]); if ($t['param'][0]) $this->jsGlobals[TYPE_OBJECT][] = $t['param'][0]; break; // error default: $target = lang::smartAI('targetUNK', [$t['type']]); } $target = $target ?: Lang::smartAI('targets', $t['type'], $t['param']); // resolve conditionals $target = preg_replace_callback('/\(([^\)]*?)\)\?([^:]*):([^;]*);/i', function ($m) { return $m[1] ? $m[2] : $m[3]; }, $target); // wrap in tooltip (suspend action-tooltip) return '[/span]'.sprintf($tooltip, $target).'[span tooltip=a-'.$this->rowKey.']'; } private function event() : array { $body = $footer = ''; $e = &$this->itr['event']; $tooltip = '[tooltip name=e-'.$this->rowKey.']'.Lang::smartAI('eventTT', array_merge([$e['type'], $e['phases'], $e['chance'], $e['flags']], $e['param'])).'[/tooltip][span tooltip=e-'.$this->rowKey.']%s[/span]'; // additional parameters $e['param'][5] = ''; $e['param'][6] = ''; switch ($e['type']) { // simple case SAI_EVENT_AGGRO: // 4 - On Creature Aggro case SAI_EVENT_DEATH: // 6 - On Creature Death case SAI_EVENT_EVADE: // 7 - On Creature Evade Attack case SAI_EVENT_RESPAWN: // 11 - On Creature/Gameobject Respawn case SAI_EVENT_REACHED_HOME: // 21 - On Creature Reached Home case SAI_EVENT_RESET: // 25 - After Combat, On Respawn or Spawn case SAI_EVENT_CHARMED: // 29 - On Creature Charmed case SAI_EVENT_CHARMED_TARGET: // 30 - On Target Charmed case SAI_EVENT_MOVEMENTINFORM: // 34 - WAYPOINT_MOTION_TYPE = 2, POINT_MOTION_TYPE = 8 case SAI_EVENT_CORPSE_REMOVED: // 36 - On Creature Corpse Removed case SAI_EVENT_AI_INIT: // 37 - case SAI_EVENT_WAYPOINT_START: // 39 - On Creature Waypoint ID Started case SAI_EVENT_WAYPOINT_REACHED: // 40 - On Creature Waypoint ID Reached case SAI_EVENT_AREATRIGGER_ONTRIGGER: // 46 - case SAI_EVENT_JUST_SUMMONED: // 54 - On Creature Just spawned case SAI_EVENT_WAYPOINT_PAUSED: // 55 - On Creature Paused at Waypoint ID case SAI_EVENT_WAYPOINT_RESUMED: // 56 - On Creature Resumed after Waypoint ID case SAI_EVENT_WAYPOINT_STOPPED: // 57 - On Creature Stopped On Waypoint ID case SAI_EVENT_WAYPOINT_ENDED: // 58 - On Creature Waypoint Path Ended case SAI_EVENT_TIMED_EVENT_TRIGGERED: // 59 - case SAI_EVENT_JUST_CREATED: // 63 - case SAI_EVENT_GOSSIP_HELLO: // 64 - On Right-Click Creature/Gameobject that have gossip enabled. case SAI_EVENT_FOLLOW_COMPLETED: // 65 - case SAI_EVENT_GO_STATE_CHANGED: // 70 - case SAI_EVENT_GO_EVENT_INFORM: // 71 - case SAI_EVENT_ACTION_DONE: // 72 - case SAI_EVENT_ON_SPELLCLICK: // 73 - case SAI_EVENT_COUNTER_SET: // 77 - If the value of specified counterID is equal to a specified value break; // num range [+ time footer] case SAI_EVENT_HEALT_PCT: // 2 - Health Percentage case SAI_EVENT_MANA_PCT: // 3 - Mana Percentage case SAI_EVENT_RANGE: // 9 - On Target In Range case SAI_EVENT_TARGET_HEALTH_PCT: // 12 - On Target Health Percentage case SAI_EVENT_TARGET_MANA_PCT: // 18 - On Target Mana Percentage case SAI_EVENT_DAMAGED: // 32 - On Creature Damaged case SAI_EVENT_DAMAGED_TARGET: // 33 - On Target Damaged case SAI_EVENT_RECEIVE_HEAL: // 53 - On Creature Received Healing case SAI_EVENT_FRIENDLY_HEALTH_PCT: // 74 - $e['param'][5] = $this->numRange('event', 0); // do not break; case SAI_EVENT_OOC_LOS: // 10 - On Target In Distance Out of Combat case SAI_EVENT_FRIENDLY_HEALTH: // 14 - On Friendly Health Deficit case SAI_EVENT_FRIENDLY_MISSING_BUFF: // 16 - On Friendly Lost Buff case SAI_EVENT_IC_LOS: // 26 - On Target In Distance In Combat case SAI_EVENT_DATA_SET: // 38 - On Creature/Gameobject Data Set, Can be used with SMART_ACTION_SET_DATA if ($time = $this->numRange('event', 2, true)) $footer = $time; break; // SAI updates case SAI_EVENT_UPDATE_IC: // 0 - In combat. case SAI_EVENT_UPDATE_OOC: // 1 - Out of combat. if ($this->srcType == SAI_SRC_TYPE_ACTIONLIST) $e['param'][6] = 1; // do not break; case SAI_EVENT_UPDATE: // 60 - $e['param'][5] = $this->numRange('event', 0, true); if ($time = $this->numRange('event', 2, true)) $footer = $time; break; case SAI_EVENT_KILL: // 5 - On Creature Kill if ($time = $this->numRange('event', 0, true)) $footer = $time; if ($e['param'][3] && !$e['param'][2]) $this->jsGlobals[TYPE_NPC][] = $e['param'][3]; break; case SAI_EVENT_SPELLHIT: // 8 - On Creature/Gameobject Spell Hit case SAI_EVENT_HAS_AURA: // 23 - On Creature Has Aura case SAI_EVENT_TARGET_BUFFED: // 24 - On Target Buffed With Spell case SAI_EVENT_SPELLHIT_TARGET: // 31 - On Target Spell Hit if ($time = $this->numRange('event', 2, true)) $footer = $time; if ($e['param'][1]) $e['param'][5] = Lang::getMagicSchools($e['param'][1]); if ($e['param'][0]) $this->jsGlobals[TYPE_SPELL][] = $e['param'][0]; break; case SAI_EVENT_VICTIM_CASTING: // 13 - On Target Casting Spell if ($e['param'][2]) $this->jsGlobals[TYPE_SPELL][$e['param'][2]]; // do not break; case SAI_EVENT_PASSENGER_BOARDED: // 27 - case SAI_EVENT_PASSENGER_REMOVED: // 28 - case SAI_EVENT_IS_BEHIND_TARGET: // 67 - On Creature is behind target. if ($time = $this->numRange('event', 0, true)) $footer = $time; break; case SAI_EVENT_SUMMONED_UNIT: // 17 - On Creature/Gameobject Summoned Unit if ($e['param'][0]) $this->jsGlobals[TYPE_NPC][] = $e['param'][0]; // do not break; case SAI_EVENT_FRIENDLY_IS_CC: // 15 - case SAI_EVENT_SUMMON_DESPAWNED: // 35 - On Summoned Unit Despawned if ($time = $this->numRange('event', 1, true)) $footer = $time; break; case SAI_EVENT_ACCEPTED_QUEST: // 19 - On Target Accepted Quest case SAI_EVENT_REWARD_QUEST: // 20 - On Target Rewarded Quest if ($e['param'][0]) $this->jsGlobals[TYPE_QUEST][] = $e['param'][0]; break; case SAI_EVENT_RECEIVE_EMOTE: // 22 - On Receive Emote. $this->jsGlobals[TYPE_EMOTE][] = $e['param'][0]; if ($time = $this->numRange('event', 1, true)) $footer = $time; break; case SAI_EVENT_TEXT_OVER: // 52 - On TEXT_OVER Event Triggered After SMART_ACTION_TALK if ($e['param'][1]) $this->jsGlobals[TYPE_NPC][] = $e['param'][1]; break; case SAI_EVENT_LINK: // 61 - Used to link together multiple events as a chain of events. $e['param'][5] = LANG::concat(DB::World()->selectCol('SELECT CONCAT("#[b]", id, "[/b]") FROM smart_scripts WHERE link = ?d AND entryorguid = ?d AND source_type = ?d', $this->itr['id'], $this->entry, $this->srcType), false); break; case SAI_EVENT_GOSSIP_SELECT: // 62 - On gossip clicked (gossip_menu_option335). $gmo = DB::World()->selectRow('SELECT gmo.OptionText AS text_loc0 {, gmol.OptionText AS text_loc?d} FROM gossip_menu_option gmo LEFT JOIN gossip_menu_option_locale gmol ON gmo.MenuID = gmol.MenuID AND gmo.OptionID = gmol.OptionID AND gmol.Locale = ?d WHERE gmo.MenuId = ?d AND gmo.OptionID = ?d', User::$localeId ? Util::$localeStrings[User::$localeId] : DBSIMPLE_SKIP, User::$localeId, $e['param'][0], $e['param'][1] ); if ($gmo) $e['param'][5] = Util::localizedString($gmo, 'text'); else trigger_error('SmartAI::event - could not find gossip menu option for event #'.$e['type']); break; case SAI_EVENT_GAME_EVENT_START: // 68 - On game_event started. case SAI_EVENT_GAME_EVENT_END: // 69 - On game_event ended. $this->jsGlobals[TYPE_WORLDEVENT][] = $e['param'][0]; break; case SAI_EVENT_DISTANCE_CREATURE: // 75 - On creature guid OR any instance of creature entry is within distance. if ($e['param'][0]) $e['param'][5] = DB::World()->selectCell('SELECT id FROM creature WHERE guid = ?d', $e['param'][0]); // do not break; case SAI_EVENT_DISTANCE_GAMEOBJECT: // 76 - On gameobject guid OR any instance of gameobject entry is within distance. if ($e['param'][0] && !$e['param'][5]) $e['param'][5] = DB::World()->selectCell('SELECT id FROM gameobject WHERE guid = ?d', $e['param'][0]); else if ($e['param'][1]) $e['param'][5] = $e['param'][1]; else if (!$e['param'][5]) trigger_error('SmartAI::event - entity for event #'.$e['type'].' not defined'); if ($e['param'][5]) $this->jsGlobals[TYPE_NPC][] = $e['param'][5]; if ($e['param'][3]) $footer = Util::foramtTime($e['param'][3], true); break; case SAI_EVENT_EVENT_PHASE_CHANGE: // 66 - On event phase mask set $e['param'][5] = Lang::concat(Util::mask2bits($a['param'][0]), false); break; default: $body = '[span class=q10]Unhandled Event[/span] #'.$e['type']; } $body = $body ?: Lang::smartAI('events', $e['type'], 0, $e['param']); if ($footer) $footer = Lang::smartAI('events', $e['type'], 1, (array)$footer); // resolve conditionals $footer = preg_replace_callback('/\(([^\)]*?)\)\?([^:]*):([^;]*);/i', function ($m) { return $m[1] ? $m[2] : $m[3]; }, $footer); $body = preg_replace_callback('/\(([^\)]*?)\)\?([^:]*):([^;]*);/i', function ($m) { return $m[1] ? $m[2] : $m[3]; }, $body); $body = str_replace('#target#', $this->target(), $body); // wrap body in tooltip return [sprintf($tooltip, $body), $footer]; } private function action() : array { $body = $footer = ''; $a = &$this->itr['action']; $tooltip = '[tooltip name=a-'.$this->rowKey.']'.Lang::smartAI('actionTT', array_merge([$a['type']], $a['param'])).'[/tooltip][span tooltip=a-'.$this->rowKey.']%s[/span]'; // additional parameters $a['param'][6] = ''; $a['param'][7] = ''; $a['param'][8] = ''; $a['param'][9] = ''; switch ($a['type']) { // simple case SAI_ACTION_ACTIVATE_GOBJECT: // 9 -> any target case SAI_ACTION_AUTO_ATTACK: // 20 -> any target case SAI_ACTION_ALLOW_COMBAT_MOVEMENT: // 21 -> self case SAI_ACTION_SET_EVENT_PHASE: // 22 -> any target case SAI_ACTION_INC_EVENT_PHASE: // 23 -> any target case SAI_ACTION_EVADE: // 24 -> any target case SAI_ACTION_COMBAT_STOP: // 27 -> self case SAI_ACTION_RANDOM_PHASE_RANGE: // 31 -> self case SAI_ACTION_RESET_GOBJECT: // 32 -> any target case SAI_ACTION_SET_INST_DATA: // 34 -> self, invoker, irrelevant case SAI_ACTION_DIE: // 37 -> self case SAI_ACTION_SET_IN_COMBAT_WITH_ZONE: // 38 -> self case SAI_ACTION_SET_INVINCIBILITY_HP_LEVEL: // 42 -> self case SAI_ACTION_SET_DATA: // 45 -> any target case SAI_ACTION_SET_VISIBILITY: // 47 -> any target case SAI_ACTION_SET_ACTIVE: // 48 -> any target case SAI_ACTION_ATTACK_START: // 49 -> any target case SAI_ACTION_KILL_UNIT: // 51 -> any target case SAI_ACTION_SET_RUN: // 59 -> self case SAI_ACTION_SET_DISABLE_GRAVITY: // 60 -> self case SAI_ACTION_SET_SWIM: // 61 -> self case SAI_ACTION_SET_COUNTER: // 63 -> any target case SAI_ACTION_STORE_TARGET_LIST: // 64 -> any target case SAI_ACTION_WP_RESUME: // 65 -> self case SAI_ACTION_PLAYMOVIE: // 68 -> invoker case SAI_ACTION_CLOSE_GOSSIP: // 72 -> any target .. doesn't matter though case SAI_ACTION_TRIGGER_TIMED_EVENT: // 73 -> self case SAI_ACTION_REMOVE_TIMED_EVENT: // 74 -> self case SAI_ACTION_OVERRIDE_SCRIPT_BASE_OBJECT: // 76 -> any?? case SAI_ACTION_RESET_SCRIPT_BASE_OBJECT: // 77 -> self case SAI_ACTION_CALL_SCRIPT_RESET: // 78 -> self case SAI_ACTION_SET_RANGED_MOVEMENT: // 79 -> self case SAI_ACTION_RANDOM_MOVE: // 89 -> any target case SAI_ACTION_SEND_GO_CUSTOM_ANIM: // 93 -> self case SAI_ACTION_SEND_GOSSIP_MENU: // 98 -> invoker case SAI_ACTION_SEND_TARGET_TO_TARGET: // 100 -> any target case SAI_ACTION_SET_HEALTH_REGEN: // 102 -> any target case SAI_ACTION_SET_ROOT: // 103 -> any target case SAI_ACTION_DISABLE_EVADE: // 117 -> self case SAI_ACTION_SET_CAN_FLY: // 119 -> self case SAI_ACTION_SET_SIGHT_DIST: // 121 -> any target case SAI_ACTION_REMOVE_ALL_GAMEOBJECTS: // 126 -> any target case SAI_ACTION_STOP_MOTION: // 127 -> any target [ye, not gonna resolve this nonsense] break; // simple type as param[0] case SAI_ACTION_PLAY_EMOTE: // 5 -> any target case SAI_ACTION_SET_EMOTE_STATE: // 17 -> any target if ($a['param'][0]) $this->jsGlobals[TYPE_EMOTE][] = $a['param'][0]; break; case SAI_ACTION_FAIL_QUEST: // 6 -> any target case SAI_ACTION_OFFER_QUEST: // 7 -> invoker case SAI_ACTION_CALL_AREAEXPLOREDOREVENTHAPPENS:// 15 -> any target case SAI_ACTION_CALL_GROUPEVENTHAPPENS: // 26 -> invoker if ($a['param'][0]) $this->jsGlobals[TYPE_QUEST][] = $a['param'][0]; break; case SAI_ACTION_REMOVEAURASFROMSPELL: // 28 -> any target case SAI_ACTION_ADD_AURA: // 75 -> any target if ($a['param'][0]) $this->jsGlobals[TYPE_SPELL][] = $a['param'][0]; break; case SAI_ACTION_CALL_KILLEDMONSTER: // 33 -> any target case SAI_ACTION_UPDATE_TEMPLATE: // 36 -> self if ($a['param'][0]) $this->jsGlobals[TYPE_NPC][] = $a['param'][0]; break; case SAI_ACTION_ADD_ITEM: // 56 -> invoker case SAI_ACTION_REMOVE_ITEM: // 57 -> invoker if ($a['param'][0]) $this->jsGlobals[TYPE_ITEM][] = $a['param'][0]; break; case SAI_ACTION_GAME_EVENT_STOP: // 111 -> doesnt matter case SAI_ACTION_GAME_EVENT_START: // 112 -> doesnt matter if ($a['param'][0]) $this->jsGlobals[TYPE_WORLDEVENT][] = $a['param'][0]; break; // simple preparse from param[0] to param[6] case SAI_ACTION_SET_REACT_STATE: // 8 -> any target $a['param'][6] = $this->reactState($a['param'][0]); break; case SAI_ACTION_SET_NPC_FLAG: // 81 -> any target case SAI_ACTION_ADD_NPC_FLAG: // 82 -> any target case SAI_ACTION_REMOVE_NPC_FLAG: // 83 -> any target $a['param'][6] = $this->npcFlags('action', 0); break; case SAI_ACTION_SET_UNIT_FIELD_BYTES_1: // 90 -> any target case SAI_ACTION_REMOVE_UNIT_FIELD_BYTES_1: // 91 -> any target $a['param'][6] = $this->unitFieldBytes1($a['param'][1], $a['param'][0]); break; case SAI_ACTION_SET_DYNAMIC_FLAG: // 94 -> any target case SAI_ACTION_ADD_DYNAMIC_FLAG: // 95 -> any target case SAI_ACTION_REMOVE_DYNAMIC_FLAG: // 96 -> any target $a['param'][6] = $this->dynFlags('action', 0); break; case SAI_ACTION_SET_GO_FLAG: // 104 -> any target case SAI_ACTION_ADD_GO_FLAG: // 105 -> any target case SAI_ACTION_REMOVE_GO_FLAG: // 106 -> any target $a['param'][6] = $this->goFlags('action', 0); break; case SAI_ACTION_SET_POWER: // 108 -> any target case SAI_ACTION_ADD_POWER: // 109 -> any target case SAI_ACTION_REMOVE_POWER: // 110 -> any target $a['param'][6] = Lang::spell('powerTypes', $a['param'][0]); break; // misc case SAI_ACTION_TALK: // 1 -> any target if ($src = $this->getTalkSource()) { if ($a['param'][6] = isset($this->quotes[$src][$a['param'][0]])) { $quotes = $this->quotes[$src][$a['param'][0]]; foreach ($quotes as $key => $q) { $_ = ($q['type'] != 4 ? $this->quotes[$src]['src'].' '.Lang::npc('textTypes', $q['type']).Lang::main('colon').($q['lang'] ? '['.$q['lang'].'] ' : null) : null).html_entity_decode($q['text']); $a['param'][7] .= '[div][span class=s'.$q['type'].']'.sprintf($_, $this->quotes[$src]['src']).'[/span][/div]'; if ($a['param'][1]) $footer = [Util::formatTime($a['param'][1], true)]; } } } else trigger_error('SmartAI::action - could not determine talk source for action #'.$a['type']); break; case SAI_ACTION_SET_FACTION: // 2 -> any target if ($a['param'][0]) { $a['param'][6] = DB::Aowow()->selectCell('SELECT factionId FROM ?_factiontemplate WHERE id = ?d', $a['param'][0]); $this->jsGlobals[TYPE_FACTION][] = $a['param'][6]; } break; case SAI_ACTION_MORPH_TO_ENTRY_OR_MODEL: // 3 -> self if ($a['param'][0]) $this->jsGlobals[TYPE_NPC][] = $a['param'][0]; else if (!$a['param'][1]) $a['param'][6] = 1; break; case SAI_ACTION_SOUND: // 4 -> self [param3 set in DB but not used in core?] $this->jsGlobals[TYPE_SOUND][] = $a['param'][0]; if ($a['param'][2]) $footer = true; break; case SAI_ACTION_RANDOM_EMOTE: // 10 -> any target $buff = []; for ($i = 0; $i < 6; $i++) { if (empty($a['param'][$i])) continue; $buff[] = '[emote='.$a['param'][$i].']'; $this->jsGlobals[TYPE_EMOTE][] = $a['param'][$i]; } $a['param'][6] = Lang::concat($buff, false); break; case SAI_ACTION_CAST: // 11 -> any target $this->jsGlobals[TYPE_SPELL][] = $a['param'][0]; if ($_ = $this->castFlags('action', 1)) $footer = $_; break; case SAI_ACTION_SUMMON_CREATURE: // 12 -> any target $this->jsGlobals[TYPE_NPC][] = $a['param'][0]; if ($a['param'][2]) $a['param'][6] = Util::formatTime($a['param'][2]); $footer = $this->summonType($a['param'][1]); break; case SAI_ACTION_THREAT_SINGLE_PCT: // 13 -> victim case SAI_ACTION_THREAT_ALL_PCT: // 14 -> self case SAI_ACTION_ADD_THREAT: // 123 -> any target $a['param'][6] = $a['param'][0] - $a['param'][1]; break; case SAI_ACTION_SET_UNIT_FLAG: // 18 -> any target case SAI_ACTION_REMOVE_UNIT_FLAG: // 19 -> any target $a['param'][6] = $a['param'][1] ? $this->unitFlags2('action', 0) : $this->unitFlags('action', 0); break; case SAI_ACTION_FLEE_FOR_ASSIST: // 25 -> none case SAI_ACTION_CALL_FOR_HELP: // 39 -> self if ($a['param'][0]) $footer = true; break; case SAI_ACTION_FOLLOW: // 29 -> any target [what the heck are param 4 & 5] $this->jsGlobals[TYPE_NPC][] = $a['param'][2]; if ($a['param'][1]) $a['param'][6] = Util::O2Deg($a['param'][1])[0]; if ($a['param'][3] || $a['param'][4]) $a['param'][7] = 1; if ($a['param'][6] || $a['param'][7]) $footer = true; break; case SAI_ACTION_RANDOM_PHASE: // 30 -> self $buff = []; for ($i = 0; $i < 7; $i++) if ($_ = $a['param'][$i]) $buff[] = $_; $a['param'][6] = Lang::concat($buff); break; case SAI_ACTION_SET_SHEATH: // 40 -> self if ($sheath = Lang::smartAI('sheaths', $a['param'][0])) $a['param'][6] = $sheath; else $a['param'][6] = lang::smartAI('sheathUNK', $a['param'][0]); break; case SAI_ACTION_FORCE_DESPAWN: // 41 -> any target $a['param'][6] = Util::formatTime($a['param'][0], true); $a['param'][7] = Util::formatTime($a['param'][1] * 1000, true); break; case SAI_ACTION_MOUNT_TO_ENTRY_OR_MODEL: // 43 -> self if ($a['param'][0]) $this->jsGlobals[TYPE_NPC][] = $a['param'][0]; else if (!$a['param'][1]) $a['param'][6] = 1; break; case SAI_ACTION_SET_INGAME_PHASE_MASK: // 44 -> any target $a['param'][6] = $a['param'][0] ? Lang::concat(Util::mask2bits($a['param'][0])) : 0; break; case SAI_ACTION_SUMMON_GO: // 50 -> self, world coords $this->jsGlobals[TYPE_OBJECT][] = $a['param'][0]; $a['param'][6] = Util::formatTime($a['param'][1] * 1000, true); if (!$a['param'][2]) $footer = true; break; case SAI_ACTION_ACTIVATE_TAXI: // 52 -> invoker $nodes = DB::Aowow()->selectRow(' SELECT tn1.name_loc0 AS start_loc0, tn1.name_loc?d AS start_loc?d, tn2.name_loc0 AS end_loc0, tn2.name_loc?d AS end_loc?d FROM ?_taxipath tp JOIN ?_taxinodes tn1 ON tp.startNodeId = tn1.id JOIN ?_taxinodes tn2 ON tp.endNodeId = tn2.id WHERE tp.id = ?d', User::$localeId, User::$localeId, User::$localeId, User::$localeId, $a['param'][0] ); $a['param'][6] = Util::localizedString($nodes, 'start'); $a['param'][7] = Util::localizedString($nodes, 'end'); break; case SAI_ACTION_WP_START: // 53 -> any .. why tho? $a['param'][7] = $this->reactState($a['param'][5]); if ($a['param'][3]) $this->jsGlobals[TYPE_QUEST][] = $a['param'][3]; if ($a['param'][4]) $a['param'][6] = Util::formatTime($a['param'][4]); if ($a['param'][2]) $footer = true; break; case SAI_ACTION_WP_PAUSE: // 54 -> self $a['param'][6] = Util::formatTime($a['param'][0]); break; case SAI_ACTION_WP_STOP: // 55 -> self if ($a['param'][0]) $a['param'][6] = Util::formatTime($a['param'][0], true); if ($a['param'][1]) { $this->jsGlobals[TYPE_QUEST][] = $a['param'][1]; $a['param'][$a['param'][2] ? 7 : 8] = 1; } break; case SAI_ACTION_INSTALL_AI_TEMPLATE: // 58 -> self $a['param'][6] = $this->aiTemplate($a['param'][0]); break; case SAI_ACTION_TELEPORT: // 62 -> invoker [resolved coords already stored in areatrigger entry] $a['param'][6] = $this->miscData['teleportA']; $this->jsGlobals[TYPE_ZONE][] = $a['param'][6]; break; case SAI_ACTION_SET_ORIENTATION: // 66 -> any target if ($this->itr['target']['type'] == SAI_TARGET_POSITION) $a['param'][6] = Util::O2Deg($this->itr['target']['pos'][3])[1]; else if ($this->itr['target']['type'] != SAI_TARGET_SELF) $a['param'][6] = '#target#'; break; case SAI_ACTION_CREATE_TIMED_EVENT: // 67 -> self $a['param'][6] = $this->numRange('action', 1, true); $a['param'][7] = ($a['param'][5] < 100); if ($repeat = $this->numRange('action', 3, true)) $footer = [$repeat]; break; case SAI_ACTION_MOVE_TO_POS: // 69 -> any target if ($a['param'][2]) $footer = true; break; case SAI_ACTION_ENABLE_TEMP_GOBJ: // 70 -> any target case SAI_ACTION_SET_CORPSE_DELAY: // 116 -> ??? case SAI_ACTION_FLEE: // 122 -> any target $a['param'][6] = Util::formatTime($a['param'][0] * 1000, true); break; case SAI_ACTION_EQUIP: // 71 -> any $buff = []; if ($a['param'][0]) { $slots = [1, 2, 3]; if ($a['param'][1]) $slots = Util::mask2bits($a['param'][1], 1); $items = DB::World()->selectRow('SELECT ItemID1, ItemID2, ItemID3 FROM creature_equip_template WHERE CreatureID = ?d AND ID = ?d', $this->miscData['baseEntry'] ?: $this->entry, $a['param'][0]); foreach ($items as $i) $this->jsGlobals[TYPE_ITEM][] = $i; foreach ($slots as $s) if ($_ = $items['ItemID'.$s]) $buff[] = '[item='.$_.']'; } else if ($a['param'][2] || $a['param'][3] || $a['param'][4]) { if ($_ = $a['param'][2]) { $this->jsGlobals[TYPE_ITEM][] = $_; $buff[] = '[item='.$_.']'; } if ($_ = $a['param'][3]) { $this->jsGlobals[TYPE_ITEM][] = $_; $buff[] = '[item='.$_.']'; } if ($_ = $a['param'][4]) { $this->jsGlobals[TYPE_ITEM][] = $_; $buff[] = '[item='.$_.']'; } } else $a['param'][7] = 1; $a['param'][6] = Lang::concat($buff); $footer = true; break; case SAI_ACTION_CALL_TIMED_ACTIONLIST: // 80 -> any target switch ($a['param'][1]) { case 0: case 1: case 2: $a['param'][6] = Lang::smartAI('saiUpdate', $a['param'][1]); break; default: $a['param'][6] = Lang::smartAI('saiUpdateUNK', [$a['param'][1]]); } $tal = new SmartAI(SAI_SRC_TYPE_ACTIONLIST, $a['param'][0], array_merge(['baseEntry' => $this->entry], $this->miscData)); $tal->prepare(); foreach ($tal->getJSGlobals() as $type => $data) { if (empty($this->jsGlobals[$type])) $this->jsGlobals[$type] = []; $this->jsGlobals[$type] = array_merge($this->jsGlobals[$type], $data); } foreach ($tal->getTabs() as $guid => $tt) $this->tabs[$guid] = $tt; break; case SAI_ACTION_SIMPLE_TALK: // 84 -> any target if ($src = $this->getTalkSource()) { if (isset($this->quotes[$src][$a['param'][0]])) { $quotes = $this->quotes[$src][$a['param'][0]]; foreach ($quotes as $key => $q) { $_ = ($q['type'] != 4 ? $this->quotes[$src]['src'].' '.Lang::npc('textTypes', $q['type']).Lang::main('colon').($q['lang'] ? '['.$q['lang'].'] ' : null) : null).html_entity_decode($q['text']); $a['param'][6] .= '[div][span class=s'.$q['type'].']'.sprintf($_, $this->quotes[$src]['src']).'[/span][/div]'; } } } else trigger_error('SmartAI::action - could not determine talk source for action #'.$a['type']); break; case SAI_ACTION_CROSS_CAST: // 86 -> entity by TargetingBlock(param3, param4, param5, param6) cross cast spell at any target $a['param'][6] = $this->target(array( 'type' => $a['param'][2], 'param' => [$a['param'][3], $a['param'][4], $a['param'][5]], 'pos' => [0, 0, 0, 0] )); // do not break; case SAI_ACTION_INVOKER_CAST: // 85 -> any target $this->jsGlobals[TYPE_SPELL][] = $a['param'][0]; if ($_ = $this->castFlags('action', 1)) $footer = $_; break; case SAI_ACTION_CALL_RANDOM_TIMED_ACTIONLIST: // 87 -> self $talBuff = []; for ($i = 0; $i < 6; $i++) { if (!$a['param'][$i]) continue; $talBuff[] = '#'.$a['param'][$i].''; $tal = new SmartAI(SAI_SRC_TYPE_ACTIONLIST, $a['param'][$i], array_merge(['baseEntry' => $this->entry], $this->miscData)); $tal->prepare(); foreach ($tal->getJSGlobals() as $type => $data) { if (empty($this->jsGlobals[$type])) $this->jsGlobals[$type] = []; $this->jsGlobals[$type] = array_merge($this->jsGlobals[$type], $data); } foreach ($tal->getTabs() as $guid => $tt) $this->tabs[$guid] = $tt; } $a['param'][6] = Lang::concat($talBuff, false); break; case SAI_ACTION_CALL_RANDOM_RANGE_TIMED_ACTIONLIST:// 88 -> self $talBuff = []; for ($i = $a['param'][0]; $i <= $a['param'][1]; $i++) { $talBuff[] = '#'.$i.''; $tal = new SmartAI(SAI_SRC_TYPE_ACTIONLIST, $i, array_merge(['baseEntry' => $this->entry], $this->miscData)); $tal->prepare(); foreach ($tal->getJSGlobals() as $type => $data) { if (empty($this->jsGlobals[$type])) $this->jsGlobals[$type] = []; $this->jsGlobals[$type] = array_merge($this->jsGlobals[$type], $data); } foreach ($tal->getTabs() as $guid => $tt) $this->tabs[$guid] = $tt; } $a['param'][6] = Lang::concat($talBuff, false); break; case SAI_ACTION_INTERRUPT_SPELL: // 92 -> self if ($_ = $a['param'][1]) $this->jsGlobals[TYPE_SPELL][] = $a['param'][1]; if ($a['param'][0] || $a['param'][2]) $footer = [$a['param'][0]]; break; case SAI_ACTION_SET_HOME_POS: // 101 -> self if ($this->itr['target']['type'] == SAI_TARGET_SELF) $a['param'][9] = 1; // do not break; case SAI_ACTION_JUMP_TO_POS: // 97 -> self case SAI_ACTION_MOVE_OFFSET: // 114 -> self $a['param'][6] = $this->itr['target']['pos'][0]; $a['param'][7] = $this->itr['target']['pos'][1]; $a['param'][8] = $this->itr['target']['pos'][2]; break; case SAI_ACTION_GO_SET_LOOT_STATE: // 99 -> any target switch ($a['param'][0]) { case 0: case 1: case 2: case 3: $a['param'][6] = Lang::smartAI('lootStates', $a['param'][0]); break; default: $a['param'][6] = Lang::smartAI('lootStateUNK', [$a['param'][0]]); } break; break; case SAI_ACTION_SUMMON_CREATURE_GROUP: // 107 -> untargeted if ($this->summons === null) $this->summons = DB::World()->selectCol('SELECT groupId AS ARRAY_KEY, entry AS ARRAY_KEY2, COUNT(*) AS n FROM creature_summon_groups WHERE summonerId = ?d GROUP BY groupId, entry', empty($this->miscData['baseEntry']) ? $this->entry : $this->miscData['baseEntry']); $buff = []; if (!empty($this->summons[$a['param'][0]])) { foreach ($this->summons[$a['param'][0]] as $id => $n) { $this->jsGlobals[TYPE_NPC][] = $id; $buff[] = $n.'x [npc='.$id.']'; } } if ($buff) $a['param'][6] = Lang::concat($buff); break; case SAI_ACTION_START_CLOSEST_WAYPOINT: // 113 -> any target $buff = []; for ($i = 0; $i < 6; $i++) if ($a['param'][$i]) $buff[] = '#[b]'.$a['param'][$i].'[/b]'; $a['param'][6] = Lang::concat($buff, false); break; case SAI_ACTION_RANDOM_SOUND: // 115 -> self for ($i = 0; $i < 4; $i++) { if ($x = $a['param'][$i]) { $this->jsGlobals[TYPE_SOUND][] = $x; $a['param'][6] .= '[sound='.$x.']'; } } if ($a['param'][5]) $footer = true; break; case SAI_ACTION_GO_SET_GO_STATE: // 118 -> ??? switch ($a['param'][0]) { case 0: case 1: case 2: $a['param'][6] = Lang::smartAI('GOStates', $a['param'][0]); break; default: $a['param'][6] = Lang::smartAI('GOStateUNK', [$a['param'][0]]); } break; case SAI_ACTION_REMOVE_AURAS_BY_TYPE: // 120 -> any target $a['param'][6] = Lang::spell('auras', $a['param'][0]); break; case SAI_ACTION_LOAD_EQUIPMENT: // 124 -> any target $buff = []; if ($a['param'][0]) { $items = DB::World()->selectRow('SELECT ItemID1, ItemID2, ItemID3 FROM creature_equip_template WHERE CreatureID = ?d AND ID = ?d', $this->miscData['baseEntry'] ?: $this->entry, $a['param'][0]); foreach ($items as $i) { if (!$i) continue; $this->jsGlobals[TYPE_ITEM][] = $i; $buff[] = '[item='.$_.']'; } } else if (!$a['param'][1]) trigger_error('SmartAI::action - action #124 (SAI_ACTION_LOAD_EQIPMENT) is malformed'); $a['param'][6] = Lang::concat($buff); $footer = true; break; case SAI_ACTION_TRIGGER_RANDOM_TIMED_EVENT: // 125 -> self $a['param'][6] = $this->numRange('action', 0); break; // todo (med): i know these exist, but have no info how they operate case SAI_ACTION_SPAWN_SPAWNGROUP: // 131 case SAI_ACTION_DESPAWN_SPAWNGROUP: // 132 case SAI_ACTION_RESPAWN_BY_SPAWNID: // 133 default: $body = Lang::smartAI('actionUNK', [$a['type']]); } $body = $body ?: Lang::smartAI('actions', $a['type'], 0, $a['param']); if (gettype($footer) != 'string') $footer = Lang::smartAI('actions', $a['type'], 1, (array)$footer); // resolve conditionals $footer = preg_replace_callback('/\(([^\)]*?)\)\?([^:]*):([^;]*);/i', function ($m) { return $m[1] ? $m[2] : $m[3]; }, $footer); $body = preg_replace_callback('/\(([^\)]*?)\)\?([^:]*):([^;]*);/i', function ($m) { return $m[1] ? $m[2] : $m[3]; }, $body); $body = str_replace('#target#', $this->target(), $body); // wrap body in tooltip return [sprintf($tooltip, $body), $footer]; } } ?>