mirror of
https://github.com/Sarjuuk/aowow.git
synced 2025-11-29 15:58:16 +08:00
1302 lines
60 KiB
PHP
1302 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_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.`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).' – '.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 <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];
|
|
}
|
|
}
|
|
|
|
?>
|