SmartAI/Conditions

* embed Conditions into SmartAI table so we can evaluate CONDITION_SOURCE_TYPE_SMART_EVENT (22)
 * make SmartAI table display flexible
This commit is contained in:
Sarjuuk
2025-11-20 23:22:17 +01:00
parent c0454917ac
commit b764200c2a
13 changed files with 119 additions and 77 deletions

View File

@@ -105,7 +105,7 @@ class AreatriggerBaseResponse extends TemplateResponse implements ICache
// tab: conditions // tab: conditions
$cnd = new Conditions(); $cnd = new Conditions();
$cnd->getBySourceEntry($this->typeId, Conditions::SRC_AREATRIGGER_CLIENT)->prepare(); $cnd->getBySource(Conditions::SRC_AREATRIGGER_CLIENT, entry: $this->typeId)->prepare();
if ($tab = $cnd->toListviewTab()) if ($tab = $cnd->toListviewTab())
{ {
$this->extendGlobalData($cnd->getJsGlobals()); $this->extendGlobalData($cnd->getJsGlobals());

View File

@@ -734,7 +734,7 @@ class ItemBaseResponse extends TemplateResponse implements ICache
$extraCols = ['$Listview.extraCols.stock', "\$Listview.funcBox.createSimpleCol('stack', 'stack', '10%', 'stack')", '$Listview.extraCols.cost']; $extraCols = ['$Listview.extraCols.stock', "\$Listview.funcBox.createSimpleCol('stack', 'stack', '10%', 'stack')", '$Listview.extraCols.cost'];
$cnd = new Conditions(); $cnd = new Conditions();
$cnd->getBySourceEntry($this->typeId, Conditions::SRC_NPC_VENDOR)->prepare(); $cnd->getBySource(Conditions::SRC_NPC_VENDOR, entry: $this->typeId)->prepare();
foreach ($sbData as $k => &$row) foreach ($sbData as $k => &$row)
{ {
$currency = []; $currency = [];

View File

@@ -385,7 +385,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
} }
$cnd = new Conditions(); $cnd = new Conditions();
$cnd->getBySourceGroup($this->typeId, Conditions::SRC_VEHICLE_SPELL)->prepare(); $cnd->getBySource(Conditions::SRC_VEHICLE_SPELL, group: $this->typeId)->prepare();
if ($cnd->toListviewColumn($controled, $extraCols, $this->typeId, 'id')) if ($cnd->toListviewColumn($controled, $extraCols, $this->typeId, 'id'))
$this->extendGlobalData($cnd->getJsGlobals()); $this->extendGlobalData($cnd->getJsGlobals());
@@ -547,7 +547,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
} }
$cnd = new Conditions(); $cnd = new Conditions();
if ($cnd->getBySourceGroup($this->typeId, Conditions::SRC_NPC_VENDOR)->prepare()) if ($cnd->getBySource(Conditions::SRC_NPC_VENDOR, group: $this->typeId)->prepare())
{ {
$this->extendGlobalData($cnd->getJsGlobals()); $this->extendGlobalData($cnd->getJsGlobals());
$cnd->toListviewColumn($lvData, $extraCols, $this->typeId, 'id'); $cnd->toListviewColumn($lvData, $extraCols, $this->typeId, 'id');
@@ -805,8 +805,8 @@ class NpcBaseResponse extends TemplateResponse implements ICache
// tab: conditions // tab: conditions
$cnd = new Conditions(); $cnd = new Conditions();
$cnd->getBySourceEntry($this->typeId, Conditions::SRC_CREATURE_TEMPLATE_VEHICLE) $cnd->getBySource(Conditions::SRC_CREATURE_TEMPLATE_VEHICLE, entry: $this->typeId)
->getBySourceGroup($this->typeId, Conditions::SRC_SPELL_CLICK_EVENT) ->getBySource(Conditions::SRC_SPELL_CLICK_EVENT, group: $this->typeId)
->getByCondition(Type::NPC, $this->typeId) ->getByCondition(Type::NPC, $this->typeId)
->prepare(); ->prepare();
if ($tab = $cnd->toListviewTab()) if ($tab = $cnd->toListviewTab())

View File

@@ -979,7 +979,7 @@ class QuestBaseResponse extends TemplateResponse implements ICache
// tab: conditions // tab: conditions
$cnd = new Conditions(); $cnd = new Conditions();
$cnd->getBySourceEntry($this->typeId, Conditions::SRC_QUEST_AVAILABLE, Conditions::SRC_QUEST_SHOW_MARK) $cnd->getBySource([Conditions::SRC_QUEST_AVAILABLE, Conditions::SRC_QUEST_SHOW_MARK], entry: $this->typeId)
->getByCondition(Type::QUEST, $this->typeId) ->getByCondition(Type::QUEST, $this->typeId)
->prepare(); ->prepare();

View File

@@ -1210,7 +1210,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
// tab: conditions // tab: conditions
$cnd = new Conditions(); $cnd = new Conditions();
$cnd->getBySourceEntry($this->typeId, Conditions::SRC_SPELL_IMPLICIT_TARGET, Conditions::SRC_SPELL, Conditions::SRC_SPELL_CLICK_EVENT, Conditions::SRC_VEHICLE_SPELL, Conditions::SRC_SPELL_PROC) $cnd->getBySource([Conditions::SRC_SPELL_IMPLICIT_TARGET, Conditions::SRC_SPELL, Conditions::SRC_SPELL_CLICK_EVENT, Conditions::SRC_VEHICLE_SPELL, Conditions::SRC_SPELL_PROC], entry: $this->typeId)
->getByCondition(Type::SPELL, $this->typeId) ->getByCondition(Type::SPELL, $this->typeId)
->prepare(); ->prepare();
if ($tab = $cnd->toListviewTab()) if ($tab = $cnd->toListviewTab())

View File

@@ -221,29 +221,26 @@ class Conditions
/* IN */ /* IN */
/******/ /******/
public function getBySourceEntry(int $entry, int ...$srcType) : self public function getBySource(int|array $type, int|array $group = 0, int|array $entry = 0, int|array $id = 0) : self
{ {
if ($group)
$group = is_int($group) ? [$group] : array_map('intVal', $group);
if ($entry)
$entry = is_int($entry) ? [$entry] : array_map('intVal', $entry);
if ($id)
$id = is_int($id) ? [$id] : array_map('intVal', $id);
if ($type)
$type = is_int($type) ? [$type] : array_map('intVal', $type);
else
return $this;
$this->rows = array_merge($this->rows, DB::World()->select( $this->rows = array_merge($this->rows, DB::World()->select(
'SELECT `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `SourceId`, `ElseGroup`, 'SELECT `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `SourceId`, `ElseGroup`,
`ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition` `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`
FROM conditions FROM conditions
WHERE `SourceTypeOrReferenceId` IN (?a) AND `SourceEntry` = ?d WHERE `SourceTypeOrReferenceId` IN (?a){ AND `SourceGroup` IN (?a)}{ AND `SourceEntry` IN (?a)}{ AND `SourceId` IN (?a)}
ORDER BY `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `ElseGroup` ASC', ORDER BY `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `ElseGroup` ASC',
$srcType, $entry $type, $group ?: DBSIMPLE_SKIP, $entry ?: DBSIMPLE_SKIP, $id ?: DBSIMPLE_SKIP
));
return $this;
}
public function getBySourceGroup(int $group, int ...$srcType) : self
{
$this->rows = array_merge($this->rows, DB::World()->select(
'SELECT `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `SourceId`, `ElseGroup`,
`ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`
FROM conditions
WHERE `SourceTypeOrReferenceId` IN (?a) AND `SourceGroup` = ?d
ORDER BY `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `ElseGroup` ASC',
$srcType, $group
)); ));
return $this; return $this;
@@ -386,6 +383,14 @@ class Conditions
return $success; return $success;
} }
public function toMarkupTag() : string
{
if (!$this->result)
return '';
return '[condition]' . json_encode($this->result, JSON_NUMERIC_CHECK) . '[/condition]';
}
public function getJsGlobals() : array public function getJsGlobals() : array
{ {
return $this->jsGlobals; return $this->jsGlobals;
@@ -486,9 +491,9 @@ class Conditions
$this->jsGlobals[$grp][$sGroup] = $sGroup; $this->jsGlobals[$grp][$sGroup] = $sGroup;
if (is_int($entry)) if (is_int($entry))
$this->jsGlobals[$entry][$sEntry] = $sEntry; $this->jsGlobals[$entry][$sEntry] = $sEntry;
// Note: sourceId currently has no typed content // Note: sourceId currently has no typed content
// if (is_int($id)) // if (is_int($id))
// $this->jsGlobals[$id][$sId] = $sId; // $this->jsGlobals[$id][$sId] = $sId;
// more checks? not all sources can retarget // more checks? not all sources can retarget
$cTarget = min(1, max(0, $cTarget)); $cTarget = min(1, max(0, $cTarget));

View File

@@ -257,8 +257,16 @@ class SmartAI
private array $result = []; private array $result = [];
private array $tabs = []; private array $tabs = [];
private array $itr = []; private array $itr = [];
private array $quotes = [];
private array $quotes = []; public string $css = <<<CSS
#smartai-generic .grid { clear:left; display: grid; }
#smartai-generic .tabbed-contents { padding:0px; clear:left; }
#smartai-generic .grid thead,
#smartai-generic .grid tbody,
#smartai-generic .grid tr { display: contents; }
#sai { display: grid; }
CSS;
// misc data // misc data
public readonly int $baseEntry; // I'm a timed action list belonging to this entry public readonly int $baseEntry; // I'm a timed action list belonging to this entry
@@ -271,24 +279,28 @@ class SmartAI
$this->title = $miscData['title'] ?? ''; $this->title = $miscData['title'] ?? '';
$this->teleportTargetArea = $miscData['teleportTargetArea'] ?? 0; $this->teleportTargetArea = $miscData['teleportTargetArea'] ?? 0;
if ($this->baseEntry) // my parent handles base css
$this->css = '';
$raw = DB::World()->select( $raw = DB::World()->select(
'SELECT `id`, `link`, 'SELECT `id`, `link`,
`event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`,
`action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `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` `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`
FROM smart_scripts FROM smart_scripts
WHERE `entryorguid` = ?d AND `source_type` = ?d WHERE `entryorguid` = ?d AND `source_type` = ?d
ORDER BY `id` ASC', ORDER BY `id` ASC',
$this->entry, $this->srcType); $this->entry, $this->srcType);
foreach ($raw as $r) foreach ($raw as $r)
{ {
$this->rawData[$r['id']] = array( $this->rawData[$r['id']] = array(
'id' => $r['id'], 'id' => $r['id'],
'link' => $r['link'], 'link' => $r['link'],
'event' => new SmartEvent($r['id'], $r['event_type'], $r['event_phase_mask'], $r['event_chance'], $r['event_flags'], [$r['event_param1'], $r['event_param2'], $r['event_param3'], $r['event_param4'], $r['event_param5']], $this), 'event' => new SmartEvent($r['id'], $r['event_type'], $r['event_phase_mask'], $r['event_chance'], $r['event_flags'], [$r['event_param1'], $r['event_param2'], $r['event_param3'], $r['event_param4'], $r['event_param5']], $this),
'action' => new SmartAction($r['id'], $r['action_type'], [$r['action_param1'], $r['action_param2'], $r['action_param3'], $r['action_param4'], $r['action_param5'], $r['action_param6']], $this), 'action' => new SmartAction($r['id'], $r['action_type'], [$r['action_param1'], $r['action_param2'], $r['action_param3'], $r['action_param4'], $r['action_param5'], $r['action_param6']], $this),
'target' => new SmartTarget($r['id'], $r['target_type'], [$r['target_param1'], $r['target_param2'], $r['target_param3'], $r['target_param4']], [$r['target_x'], $r['target_y'], $r['target_z'], $r['target_o']], $this) 'target' => new SmartTarget($r['id'], $r['target_type'], [$r['target_param1'], $r['target_param2'], $r['target_param3'], $r['target_param4']], [$r['target_x'], $r['target_y'], $r['target_z'], $r['target_o']], $this),
'condition' => (new Conditions())->getBySource(Conditions::SRC_SMART_EVENT, $r['id'] + 1, $entry, $srcType)
); );
} }
} }
@@ -619,10 +631,9 @@ class SmartAI
if ($this->result) if ($this->result)
return true; return true;
$hidePhase = $visibleCols = (1 << 0) | (1 << 2) | (1 << 4);
$hideChance = true;
foreach ($this->iterate() as $id => $__) foreach ($this->iterate() as $__)
{ {
$rowIdx = Util::createHash(8); $rowIdx = Util::createHash(8);
@@ -636,53 +647,60 @@ class SmartAI
$evtBody = str_replace(['#target#', '#rowIdx#'], [$this->itr['target']->process(), $rowIdx], $evtBody); $evtBody = str_replace(['#target#', '#rowIdx#'], [$this->itr['target']->process(), $rowIdx], $evtBody);
$actBody = str_replace(['#target#', '#rowIdx#'], [$this->itr['target']->process(), $rowIdx], $actBody); $actBody = str_replace(['#target#', '#rowIdx#'], [$this->itr['target']->process(), $rowIdx], $actBody);
if (!$this->itr['event']->hasPhases()) if ($this->itr['event']->hasPhases())
$hidePhase = false; $visibleCols |= (1 << 1);
if ($this->itr['event']->chance != 100) if ($this->itr['event']->chance != 100)
$hideChance = false; $visibleCols |= (1 << 3);
if ($this->itr['condition']->prepare())
{
$visibleCols |= (1 << 5);
Util::mergeJsGlobals($this->jsGlobals, $this->itr['condition']->getJsGlobals());
}
$this->result[] = array( $this->result[] = array(
$this->itr['id'], $this->itr['id'],
implode(', ', Util::mask2bits($this->itr['event']->phaseMask, 1)), implode(', ', Util::mask2bits($this->itr['event']->phaseMask, 1)),
$evtBody.($evtFooter ? '[div float=right margin=0px clear=both][i][small class=q0]'.$evtFooter.'[/small][/i][/div]' : null), $evtBody.($evtFooter ? '[div float=right margin=0px clear=both width=100% align=right][i][small class=q0]'.$evtFooter.'[/small][/i][/div]' : ''),
$this->itr['event']->chance.'%', $this->itr['event']->chance.'%',
$actBody.($actFooter ? '[div float=right margin=0px clear=both][i][small class=q0]'.$actFooter.'[/small][/i][/div]' : null) $actBody.($actFooter ? '[div float=right margin=0px clear=both width=100% align=right][i][small class=q0]'.$actFooter.'[/small][/i][/div]' : ''),
$this->itr['condition']->toMarkupTag()
); );
} }
$th = array( $th = array(
'#' => 16, ['#' , '24px'],
'Phase' => 32, ['Phase', '48px'],
'Event' => 350, ['Event', '30%%'],
'Chance' => 24, ['Chance', '60px'],
'Action' => 0 ['Action', 'auto'],
['Condition', 'auto']
); );
if ($hidePhase) for ($i = 0, $j = count($th); $i < $j; $i++)
{ {
unset($th['Phase']); if ($visibleCols & (1 << $i))
foreach ($this->result as &$r) continue;
unset($r[1]);
}
unset($r);
if ($hideChance) unset($th[$i]);
{
unset($th['Chance']);
foreach ($this->result as &$r) foreach ($this->result as &$r)
unset($r[3]); unset($r[$i]);
}
unset($r);
$tbl = '[tr]'; unset($r);
foreach ($th as $n => $w) }
$tbl .= '[td header '.($w ? 'width='.$w.'px' : null).']'.$n.'[/td]';
$tbl .= '[/tr]'; $tblId = Util::createHash(12);
$this->css .= "\n#tbl-".$tblId." { grid-template-columns: ".implode(' ', array_column($th, 1))."; }";
$tbl = '[tr]' . array_reduce(array_column($th, 0), fn($out, $n) => $out .= '[td header]'.$n.'[/td]', '') . '[/tr]';
foreach ($this->result as $r) foreach ($this->result as $r)
$tbl .= '[tr][td]'.implode('[/td][td]', $r).'[/td][/tr]'; $tbl .= '[tr][td]'.implode('[/td][td]', $r).'[/td][/tr]';
$tbl = '[table id=tbl-'.$tblId.' class=grid]'.$tbl.'[/table]';
if ($this->srcType == self::SRC_TYPE_ACTIONLIST) if ($this->srcType == self::SRC_TYPE_ACTIONLIST)
$this->tabs[$this->entry] = $tbl; $this->tabs[$this->entry] = $tbl;
else else
@@ -698,16 +716,16 @@ class SmartAI
if (!$this->rawData) if (!$this->rawData)
return null; return null;
$wrapper = '[table class=grid width=940px]%s[/table]'; $wrapper = '%s';
$return = '[style]#smartai-generic .grid { clear:left; } #smartai-generic .tabbed-contents { padding:0px; clear:left; }[/style][pad][h3][toggler id=sai]SmartAI'.$this->title.'[/toggler][/h3][div id=sai clear=left]%s[/div]'; $return = '[style]'.strtr($this->css, "\n", ' ').'[/style][pad][h3][toggler id=sai]SmartAI'.$this->title.'[/toggler][/h3][div id=sai clear=left]%s[/div]';
$tabs = ''; $tabs = '';
if (count($this->tabs) > 1) if (count($this->tabs) > 1)
{ {
$wrapper = '[tabs name=sai width=942px]%s[/tabs]'; $wrapper = '[tabs name=sai]%s[/tabs]';
$return = "[script]function TalTabClick(id) { $('#dsf67g4d-sai').find('[href=\'#sai-actionlist-' + id + '\']').click(); }[/script]" . $return; $return = "[script]function TalTabClick(id) { $('#dsf67g4d-sai').find('[href=\'#sai-actionlist-' + id + '\']').click(); }[/script]" . $return;
foreach ($this->tabs as $guid => $data) foreach ($this->tabs as $guid => $data)
{ {
$buff = '[tab name="'.($guid ? 'ActionList #'.$guid : 'Main').'"][table class=grid width=940px]'.$data.'[/table][/tab]'; $buff = '[tab name="'.($guid ? 'ActionList #'.$guid : 'Main').'"]'.$data.'[/tab]';
if ($guid) if ($guid)
$tabs .= $buff; $tabs .= $buff;
else else

View File

@@ -562,6 +562,8 @@ class SmartAction
$tal = new SmartAI(SmartAI::SRC_TYPE_ACTIONLIST, $this->param[0], ['baseEntry' => $this->smartAI->getEntry()]); $tal = new SmartAI(SmartAI::SRC_TYPE_ACTIONLIST, $this->param[0], ['baseEntry' => $this->smartAI->getEntry()]);
$tal->prepare(); $tal->prepare();
$this->smartAI->css .= $tal->css;
Util::mergeJsGlobals($this->jsGlobals, $tal->getJSGlobals()); Util::mergeJsGlobals($this->jsGlobals, $tal->getJSGlobals());
foreach ($tal->getTabs() as $guid => $tt) foreach ($tal->getTabs() as $guid => $tt)
@@ -587,6 +589,8 @@ class SmartAction
$tal = new SmartAI(SmartAI::SRC_TYPE_ACTIONLIST, $this->param[$i], ['baseEntry' => $this->smartAI->getEntry()]); $tal = new SmartAI(SmartAI::SRC_TYPE_ACTIONLIST, $this->param[$i], ['baseEntry' => $this->smartAI->getEntry()]);
$tal->prepare(); $tal->prepare();
$this->smartAI->css .= $tal->css;
Util::mergeJsGlobals($this->jsGlobals, $tal->getJSGlobals()); Util::mergeJsGlobals($this->jsGlobals, $tal->getJSGlobals());
foreach ($tal->getTabs() as $guid => $tt) foreach ($tal->getTabs() as $guid => $tt)
@@ -603,6 +607,8 @@ class SmartAction
$tal = new SmartAI(SmartAI::SRC_TYPE_ACTIONLIST, $i, ['baseEntry' => $this->smartAI->getEntry()]); $tal = new SmartAI(SmartAI::SRC_TYPE_ACTIONLIST, $i, ['baseEntry' => $this->smartAI->getEntry()]);
$tal->prepare(); $tal->prepare();
$this->smartAI->css .= $tal->css;
Util::mergeJsGlobals($this->jsGlobals, $tal->getJSGlobals()); Util::mergeJsGlobals($this->jsGlobals, $tal->getJSGlobals());
foreach ($tal->getTabs() as $guid => $tt) foreach ($tal->getTabs() as $guid => $tt)

View File

@@ -368,7 +368,7 @@ class SmartEvent
public function hasPhases() : bool public function hasPhases() : bool
{ {
return $this->phaseMask == 0; return $this->phaseMask && ($this->phaseMask & 0xFFF) != 0xFFF;
} }
private function formatFlags() : string private function formatFlags() : string

View File

@@ -138,7 +138,7 @@ class LootByContainer extends Loot
$groupChances[$k] = (100 - $sum) / ($nGroupEquals[$k] ?: 1); $groupChances[$k] = (100 - $sum) / ($nGroupEquals[$k] ?: 1);
} }
if ($cnd->getBySourceGroup($lootId, Conditions::lootTableToConditionSource($tableName))->prepare()) if ($cnd->getBySource(Conditions::lootTableToConditionSource($tableName), group: $lootId)->prepare())
{ {
$this->storeJSGlobals($cnd->getJsGlobals()); $this->storeJSGlobals($cnd->getJsGlobals());
$cnd->toListviewColumn($loot, $this->extraCols, $lootId, 'content'); $cnd->toListviewColumn($loot, $this->extraCols, $lootId, 'content');

View File

@@ -171,7 +171,7 @@ class LootByItem extends Loot
if ($newRefs) if ($newRefs)
{ {
$cnd = new Conditions(); $cnd = new Conditions();
if ($cnd->getBySourceEntry($this->entry, Conditions::SRC_REFERENCE_LOOT_TEMPLATE)) if ($cnd->getBySource(Conditions::SRC_REFERENCE_LOOT_TEMPLATE, entry: $this->entry))
if ($cnd->toListviewColumn($newRefs, $x, $this->entry)) if ($cnd->toListviewColumn($newRefs, $x, $this->entry))
$this->storejsGlobals($cnd->getJsGlobals()); $this->storejsGlobals($cnd->getJsGlobals());
} }

View File

@@ -0,0 +1 @@
UPDATE `aowow_dbversion` SET `build` = CONCAT(IFNULL(`build`, ''), ' globaljs');

View File

@@ -465,6 +465,18 @@ var Markup = {
return [str, '</span>']; return [str, '</span>'];
} }
}, },
condition:
{
ltrim: true,
rtrim: true,
empty: false,
allowedClass: MARKUP_CLASS_STAFF,
allowedChildren: { '<text>': 1 },
toHtml: function(attr)
{
return ['<span>' + Markup.toHtml(ConditionList.createCell(JSON.parse(attr._nodes[0].attr._rawText)), { skipReset: true }) + '</span>'];
}
},
copy: copy:
{ {
empty: false, empty: false,