Files
aowow/includes/smartAI.class.php
Sarjuuk 54b20c3131 NPC/Spells
* always treat spells from creature_template_spell as controlled abilities
2020-02-26 17:29:33 +01:00

1296 lines
60 KiB
PHP

<?php
if (!defined('AOWOW_REVISION'))
die('illegal access');
class SmartAI
{
private $jsGlobals = [];
private $rawData = [];
private $result = [];
private $tabs = [];
private $itr = [];
private $srcType = 0;
private $entry = 0;
private $miscData = [];
private $quotes = [];
private $summons = null;
public function __construct(int $srcType, int $entry, array $miscData = [])
{
$this->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_param4, 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'], $r['target_param4']],
'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 .= ' &ndash; '.($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.`LanguageID`, ct.`Language`) AS lang,
IFNULL(NULLIF(bct.Text, ""), IFNULL(NULLIF(bct.Text1, ""), IFNULL(ct.`Text`, ""))) AS text_loc0,
{IFNULL(NULLIF(bctl.Text, ""), IFNULL(NULLIF(bctl.Text1, ""), IFNULL(ctl.Text, ""))) AS text_loc?d,}
IF(bct.SoundEntriesID > 0, bct.SoundEntriesID, 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).' &ndash; '.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]';
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]';
// init additional parameters
$a['param'] = array_pad($a['param'], 10, '');
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], true);
$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], true);
if ($a['param'][2])
$footer = true;
break;
case SAI_ACTION_WP_PAUSE: // 54 -> self
$a['param'][6] = Util::formatTime($a['param'][0], true);
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 <param1> 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 href=#sai-actionlist-'.$a['param'][$i].' onclick=\\"\$(\\\'#dsf67g4d-sai\\\').find(\\\'[href=\\\\\'#sai-actionlist-'.$a['param'][$i].'\\\\\']\\\').click()\\">#'.$a['param'][$i].'</a>';
$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[] = '<a href=#sai-actionlist-'.$i.' onclick=\\"\$(\\\'#dsf67g4d-sai\\\').find(\\\'[href=\\\\\'#sai-actionlist-'.$i.'\\\\\']\\\').click()\\">#'.$i.'</a>';
$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];
}
}
?>