0: type, array: [fn, newIdx, extraParam]; error class: int self::EVENT_UPDATE_IC => [['numRange', 10, true], null, ['numRange', -1, true], null, null, 0], // InitialMin, InitialMax, RepeatMin, RepeatMax self::EVENT_UPDATE_OOC => [['numRange', 10, true], null, ['numRange', -1, true], null, null, 0], // InitialMin, InitialMax, RepeatMin, RepeatMax self::EVENT_HEALTH_PCT => [['numRange', 10, false], null, ['numRange', -1, true], null, null, 0], // HPMin%, HPMax%, RepeatMin, RepeatMax self::EVENT_MANA_PCT => [['numRange', 10, false], null, ['numRange', -1, true], null, null, 0], // ManaMin%, ManaMax%, RepeatMin, RepeatMax self::EVENT_AGGRO => [null, null, null, null, null, 0], // NONE self::EVENT_KILL => [['numRange', -1, true], null, null, Type::NPC, null, 0], // CooldownMin0, CooldownMax1, playerOnly2, else creature entry3 self::EVENT_DEATH => [null, null, null, null, null, 0], // NONE self::EVENT_EVADE => [null, null, null, null, null, 0], // NONE self::EVENT_SPELLHIT => [Type::SPELL, ['magicSchool', 10, false], ['numRange', -1, true], null, null, 0], // SpellID, School, CooldownMin, CooldownMax self::EVENT_RANGE => [['numRange', 10, false], null, ['numRange', -1, true], null, null, 0], // MinDist, MaxDist, RepeatMin, RepeatMax self::EVENT_OOC_LOS => [['hostilityMode', 10, false], null, ['numRange', -1, true], null, null, 0], // hostilityModes, MaxRange, CooldownMin, CooldownMax self::EVENT_RESPAWN => [null, null, Type::ZONE, null, null, 0], // type, MapId, ZoneId self::EVENT_TARGET_HEALTH_PCT => [['numRange', 10, false], null, ['numRange', -1, true], null, null, 1], // UNUSED, DO NOT REUSE self::EVENT_VICTIM_CASTING => [['numRange', -1, true], null, Type::SPELL, null, null, 0], // RepeatMin, RepeatMax, spellid self::EVENT_FRIENDLY_HEALTH => [null, null, ['numRange', -1, true], null, null, 1], // UNUSED, DO NOT REUSE self::EVENT_FRIENDLY_IS_CC => [null, ['numRange', -1, true], null, null, null, 0], // Radius, RepeatMin, RepeatMax self::EVENT_FRIENDLY_MISSING_BUFF => [Type::SPELL, null, ['numRange', -1, true], null, null, 0], // SpellId, Radius, RepeatMin, RepeatMax self::EVENT_SUMMONED_UNIT => [Type::NPC, ['numRange', -1, true], null, null, null, 0], // CreatureId(0 all), CooldownMin, CooldownMax self::EVENT_TARGET_MANA_PCT => [['numRange', 10, false], null, ['numRange', -1, true], null, null, 1], // UNUSED, DO NOT REUSE self::EVENT_ACCEPTED_QUEST => [Type::QUEST, ['numRange', -1, true], null, null, null, 0], // QuestID (0 = any), CooldownMin, CooldownMax self::EVENT_REWARD_QUEST => [Type::QUEST, ['numRange', -1, true], null, null, null, 0], // QuestID (0 = any), CooldownMin, CooldownMax self::EVENT_REACHED_HOME => [null, null, null, null, null, 0], // NONE self::EVENT_RECEIVE_EMOTE => [Type::EMOTE, ['numRange', -1, true], null, null, null, 0], // EmoteId, CooldownMin, CooldownMax, condition, val1, val2, val3 self::EVENT_HAS_AURA => [Type::SPELL, null, ['numRange', -1, true], null, null, 0], // Param1 = SpellID, Param2 = Stack amount, Param3/4 RepeatMin, RepeatMax self::EVENT_TARGET_BUFFED => [Type::SPELL, null, ['numRange', -1, true], null, null, 0], // Param1 = SpellID, Param2 = Stack amount, Param3/4 RepeatMin, RepeatMax self::EVENT_RESET => [null, null, null, null, null, 0], // Called after combat, when the creature respawn and spawn. self::EVENT_IC_LOS => [['hostilityMode', 10, false], null, ['numRange', -1, true], null, null, 0], // hostilityModes, MaxRnage, CooldownMin, CooldownMax self::EVENT_PASSENGER_BOARDED => [['numRange', -1, true], null, null, null, null, 0], // CooldownMin, CooldownMax self::EVENT_PASSENGER_REMOVED => [['numRange', -1, true], null, null, null, null, 0], // CooldownMin, CooldownMax self::EVENT_CHARMED => [null, null, null, null, null, 0], // onRemove (0 - on apply, 1 - on remove) self::EVENT_CHARMED_TARGET => [null, null, null, null, null, 1], // UNUSED, DO NOT REUSE self::EVENT_SPELLHIT_TARGET => [Type::SPELL, ['magicSchool', 10, false], ['numRange', -1, true], null, null, 0], // SpellID, School, CooldownMin, CooldownMax self::EVENT_DAMAGED => [['numRange', 10, false], null, ['numRange', -1, true], null, null, 0], // MinDmg, MaxDmg, CooldownMin, CooldownMax self::EVENT_DAMAGED_TARGET => [['numRange', 10, false], null, ['numRange', -1, true], null, null, 0], // MinDmg, MaxDmg, CooldownMin, CooldownMax self::EVENT_MOVEMENTINFORM => [['motionType', 10, false], null, null, null, null, 0], // MovementType(any), PointID self::EVENT_SUMMON_DESPAWNED => [Type::NPC, ['numRange', -1, true], null, null, null, 0], // Entry, CooldownMin, CooldownMax self::EVENT_CORPSE_REMOVED => [null, null, null, null, null, 0], // NONE self::EVENT_AI_INIT => [null, null, null, null, null, 0], // NONE self::EVENT_DATA_SET => [null, null, ['numRange', -1, true], null, null, 0], // Id, Value, CooldownMin, CooldownMax self::EVENT_WAYPOINT_START => [null, null, null, null, null, 1], // UNUSED, DO NOT REUSE self::EVENT_WAYPOINT_REACHED => [null, null, null, null, null, 0], // PointId(0any), pathID(0any) self::EVENT_TRANSPORT_ADDPLAYER => [null, null, null, null, null, 2], // NONE self::EVENT_TRANSPORT_ADDCREATURE => [null, null, null, null, null, 2], // Entry (0 any) self::EVENT_TRANSPORT_REMOVE_PLAYER => [null, null, null, null, null, 2], // NONE self::EVENT_TRANSPORT_RELOCATE => [null, null, null, null, null, 2], // PointId self::EVENT_INSTANCE_PLAYER_ENTER => [null, null, null, null, null, 2], // Team (0 any), CooldownMin, CooldownMax self::EVENT_AREATRIGGER_ONTRIGGER => [Type::AREATRIGGER, null, null, null, null, 0], // TriggerId(0 any) self::EVENT_QUEST_ACCEPTED => [null, null, null, null, null, 2], // none self::EVENT_QUEST_OBJ_COMPLETION => [null, null, null, null, null, 2], // none self::EVENT_QUEST_COMPLETION => [null, null, null, null, null, 2], // none self::EVENT_QUEST_REWARDED => [null, null, null, null, null, 2], // none self::EVENT_QUEST_FAIL => [null, null, null, null, null, 2], // none self::EVENT_TEXT_OVER => [null, Type::NPC, null, null, null, 0], // GroupId from creature_text, creature entry who talks (0 any) self::EVENT_RECEIVE_HEAL => [['numRange', 10, false], null, ['numRange', -1, true], null, null, 0], // MinHeal, MaxHeal, CooldownMin, CooldownMax self::EVENT_JUST_SUMMONED => [null, null, null, null, null, 0], // none self::EVENT_WAYPOINT_PAUSED => [null, null, null, null, null, 0], // PointId(0any), pathID(0any) self::EVENT_WAYPOINT_RESUMED => [null, null, null, null, null, 0], // PointId(0any), pathID(0any) self::EVENT_WAYPOINT_STOPPED => [null, null, null, null, null, 0], // PointId(0any), pathID(0any) self::EVENT_WAYPOINT_ENDED => [null, null, null, null, null, 0], // PointId(0any), pathID(0any) self::EVENT_TIMED_EVENT_TRIGGERED => [null, null, null, null, null, 0], // id self::EVENT_UPDATE => [['numRange', 10, true], null, ['numRange', -1, true], null, null, 0], // InitialMin, InitialMax, RepeatMin, RepeatMax self::EVENT_LINK => [null, null, null, null, null, 0], // INTERNAL USAGE, no params, used to link together multiple events, does not use any extra resources to iterate event lists needlessly self::EVENT_GOSSIP_SELECT => [null, null, null, null, null, 0], // menuID, actionID self::EVENT_JUST_CREATED => [null, null, null, null, null, 0], // none self::EVENT_GOSSIP_HELLO => [null, null, null, null, null, 0], // noReportUse (for GOs) self::EVENT_FOLLOW_COMPLETED => [null, null, null, null, null, 0], // none self::EVENT_EVENT_PHASE_CHANGE => [null, null, null, null, null, 1], // UNUSED, DO NOT REUSE self::EVENT_IS_BEHIND_TARGET => [['numRange', -1, true], null, null, null, null, 1], // UNUSED, DO NOT REUSE self::EVENT_GAME_EVENT_START => [Type::WORLDEVENT, null, null, null, null, 0], // game_event.Entry self::EVENT_GAME_EVENT_END => [Type::WORLDEVENT, null, null, null, null, 0], // game_event.Entry self::EVENT_GO_LOOT_STATE_CHANGED => [['lootState', 10, false], null, null, null, null, 0], // go LootState self::EVENT_GO_EVENT_INFORM => [null, null, null, null, null, 0], // eventId self::EVENT_ACTION_DONE => [null, null, null, null, null, 0], // eventId (SharedDefines.EventId) self::EVENT_ON_SPELLCLICK => [null, null, null, null, null, 0], // clicker (unit) self::EVENT_FRIENDLY_HEALTH_PCT => [['numRange', 10, false], null, ['numRange', -1, true], null, null, 0], // minHpPct, maxHpPct, repeatMin, repeatMax self::EVENT_DISTANCE_CREATURE => [null, Type::NPC, null, ['numRange', -1, true], null, 0], // guid, entry, distance, repeat self::EVENT_DISTANCE_GAMEOBJECT => [null, Type::OBJECT, null, ['numRange', -1, true], null, 0], // guid, entry, distance, repeat self::EVENT_COUNTER_SET => [null, null, ['numRange', -1, true], null, null, 0], // id, value, cooldownMin, cooldownMax self::EVENT_SCENE_START => [null, null, null, null, null, 2], // don't use on 3.3.5a self::EVENT_SCENE_TRIGGER => [null, null, null, null, null, 2], // don't use on 3.3.5a self::EVENT_SCENE_CANCEL => [null, null, null, null, null, 2], // don't use on 3.3.5a self::EVENT_SCENE_COMPLETE => [null, null, null, null, null, 2], // don't use on 3.3.5a self::EVENT_SUMMONED_UNIT_DIES => [Type::NPC, ['numRange', -1, true], null, null, null, 0], // CreatureId(0 all), CooldownMin, CooldownMax self::EVENT_ON_SPELL_CAST => [Type::SPELL, ['numRange', -1, true], null, null, null, 0], // SpellID, CooldownMin, CooldownMax self::EVENT_ON_SPELL_FAILED => [Type::SPELL, ['numRange', -1, true], null, null, null, 0], // SpellID, CooldownMin, CooldownMax self::EVENT_ON_SPELL_START => [Type::SPELL, ['numRange', -1, true], null, null, null, 0], // SpellID, CooldownMin, CooldownMax self::EVENT_ON_DESPAWN => [null, null, null, null, null, 0] // NONE ); private array $jsGlobals = []; public function __construct( private int $id, public readonly int $type, public readonly int $phaseMask, public readonly int $chance, private int $flags, private array $param, private SmartAI &$smartAI) { // additional parameters Util::checkNumeric($this->param, NUM_CAST_INT); $this->param = array_pad($this->param, 15, ''); } public function process() : array { $body = $footer = ''; $phases = Util::mask2bits($this->phaseMask, 1) ?: [0]; $eventTT = Lang::smartAI('eventTT', array_merge([$this->type, $phases, $this->chance, $this->flags], $this->param)); for ($i = 0; $i < 5; $i++) { $eParams = $this->data[$this->type]; if (is_array($eParams[$i])) { [$fn, $idx, $extraParam] = $eParams[$i]; if ($idx < 0) $footer = $this->{$fn}($this->param[$i], $this->param[$i + 1], $extraParam); else $this->param[$idx] = $this->{$fn}($this->param[$i], $this->param[$i + 1], $extraParam); } else if (is_int($eParams[$i]) && $this->param[$i]) $this->jsGlobals[$eParams[$i]][$this->param[$i]] = $this->param[$i]; } // non-generic cases switch ($this->type) { case self::EVENT_UPDATE_IC: // 0 - In combat. case self::EVENT_UPDATE_OOC: // 1 - Out of combat. if ($this->smartAI->srcType == SmartAI::SRC_TYPE_ACTIONLIST) $this->param[11] = 1; // do not break; case self::EVENT_GOSSIP_HELLO: // 64 - On Right-Click Creature/Gameobject that have gossip enabled. if ($this->smartAI->srcType == SmartAI::SRC_TYPE_OBJECT) $footer = array( $this->param[0] == 1, $this->param[0] == 2, ); break; case self::EVENT_RESPAWN: // 11 - On Creature/Gameobject Respawn in Zone/Map if ($this->param[0] == 1) // per map { switch ($this->param[1]) { case 0: $this->param[10] = Lang::maps('EasternKingdoms'); break; case 1: $this->param[10] = Lang::maps('Kalimdor'); break; case 530: $this->param[10] = Lang::maps('Outland'); break; case 571: $this->param[10] = Lang::maps('Northrend'); break; default: if ($aId = DB::Aowow()->selectCell('SELECT `id` FROM ?_zones WHERE `mapId` = ?d', $this->param[1])) { $this->param[11] = $aId; $this->jsGlobals[Type::ZONE][$aId] = $aId; } else $this->param[11] = '[span class=q10]Unknown Map[/span] #'.$this->param[1]; }; } else if ($this->param[0] == 2) // per zone $this->param[11] = $this->param[2]; break; case self::EVENT_LINK: // 61 - Used to link together multiple events as a chain of events. if ($links = DB::World()->selectCol('SELECT `id` FROM smart_scripts WHERE `link` = ?d AND `entryorguid` = ?d AND `source_type` = ?d', $this->id, $this->smartAI->entry, $this->smartAI->srcType)) $this->param[10] = Lang::concat($links, Lang::CONCAT_OR, fn($x) => "#[b]".$x."[/b]"); break; case self::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', Lang::getLocale() != Locale::EN ? Lang::getLocale()->value : DBSIMPLE_SKIP, Lang::getLocale()->json(), $this->param[0], $this->param[1] ); if ($gmo) $this->param[10] = Util::jsEscape(Util::localizedString($gmo, 'text')); else trigger_error('SmartAI::event - could not find gossip menu option for event #'.$this->type); break; case self::EVENT_DISTANCE_CREATURE: // 75 - On creature guid OR any instance of creature entry is within distance. if ($this->param[0]) if ($_ = $this->resolveGuid(Type::NPC, $this->param[0])) { $this->jsGlobals[Type::NPC][$this->param[0]] = $this->param[0]; $this->param[10] = $_; } // do not break; case self::EVENT_DISTANCE_GAMEOBJECT: // 76 - On gameobject guid OR any instance of gameobject entry is within distance. if ($this->param[0] && !$this->param[10]) { if ($_ = $this->resolveGuid(Type::OBJECT, $this->param[0])) { $this->jsGlobals[Type::OBJECT][$this->param[0]] = $this->param[0]; $this->param[10] = $_; } } else if ($this->param[1]) $this->param[10] = $this->param[1]; else if (!$this->param[10]) trigger_error('SmartAI::event - entity for event #'.$this->type.' not defined'); break; case self::EVENT_EVENT_PHASE_CHANGE: // 66 - On event phase mask set $this->param[10] = Lang::concat(Util::mask2bits($this->param[0]), Lang::CONCAT_OR); break; } $this->smartAI->addJsGlobals($this->jsGlobals); $body = Lang::smartAI('events', $this->type, 0, $this->param) ?? Lang::smartAI('eventUNK', [$this->type]); if ($footer) $footer = Lang::smartAI('events', $this->type, 1, (array)$footer); // resolve conditionals $i = 0; while (strstr($body, ')?') && $i++ < 3) $body = preg_replace_callback('/\(([^\)]*?)\)\?([^:]*):(([^;]*);*);/i', fn($m) => $m[1] ? $m[2] : $m[3], $body); $i = 0; while (strstr($footer, ')?') && $i++ < 3) $footer = preg_replace_callback('/\(([^\)]*?)\)\?([^:]*):(([^;]*);*);/i', fn($m) => $m[1] ? $m[2] : $m[3], $footer); if ($_ = $this->formatFlags()) $footer = $_ . ($footer ? '; '.$footer : ''); if (User::isInGroup(U_GROUP_EMPLOYEE)) { if ($eParams[5] == 1) $footer = '[span class=rep2]DEPRECATED[/span] ' . $footer; else if ($eParams[5] == 2) $footer = '[span class=rep0]RESERVED[/span] ' . $footer; } // wrap body in tooltip return [sprintf(self::EVENT_CELL_TPL, $eventTT, $body), $footer]; } public function hasPhases() : bool { return $this->phaseMask == 0; } private function formatFlags() : string { $flags = $this->flags; if (($flags & self::FLAG_ALL_DIFFICULTIES) == self::FLAG_ALL_DIFFICULTIES) $flags &= ~self::FLAG_ALL_DIFFICULTIES; $ef = []; for ($i = 1; $i <= self::FLAG_WHILE_CHARMED; $i <<= 1) if ($flags & $i) if ($x = Lang::smartAI('eventFlags', $i)) $ef[] = $x; return Lang::concat($ef); } } ?>