OregonCore  revision fb2a440-git
Your Favourite TBC server
CreatureEventAIMgr.cpp
Go to the documentation of this file.
1 
2 /*
3  * This file is part of the OregonCore Project. See AUTHORS file for Copyright information
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; either version 2 of the License, or (at your
8  * option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13  * more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "Common.h"
20 #include "Database/DatabaseEnv.h"
21 #include "DBCStores.h"
22 #include "Database/SQLStorage.h"
23 #include "CreatureEventAI.h"
24 #include "CreatureEventAIMgr.h"
25 #include "ObjectMgr.h"
26 #include "ObjectGuid.h"
27 #include "GridDefines.h"
28 #include "ConditionMgr.h"
29 
31 
32 // -------------------
34 {
35  // Drop Existing Text Map, only done once and we are ready to add data from multiple sources.
37 
38  // Load EventAI Text
40 
41  // Gather Additional data from EventAI Texts
42  QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry, sound, type, language, emote FROM creature_ai_texts");
43 
44  if (result)
45  {
46  uint32 count = 0;
47 
48  do
49  {
50  Field* fields = result->Fetch();
51  StringTextData temp;
52 
53  int32 i = fields[0].GetInt32();
54  temp.SoundId = fields[1].GetInt32();
55  temp.Type = fields[2].GetInt32();
56  temp.Language = fields[3].GetInt32();
57  temp.Emote = fields[4].GetInt32();
58 
59  // range negative
61  {
62  sLog.outErrorDb("CreatureEventAI: Entry %i in table creature_ai_texts is not in valid range(%d-%d)", i, MIN_CREATURE_AI_TEXT_STRING_ID, MAX_CREATURE_AI_TEXT_STRING_ID);
63  continue;
64  }
65 
66  // range negative (don't must be happen, loaded from same table)
67  if (!sObjectMgr.GetOregonStringLocale(i))
68  {
69  sLog.outErrorDb("CreatureEventAI: Entry %i in table creature_ai_texts not found", i);
70  continue;
71  }
72 
73  if (temp.SoundId)
74  {
75  if (!sSoundEntriesStore.LookupEntry(temp.SoundId))
76  sLog.outErrorDb("CreatureEventAI: Entry %i in table creature_ai_texts has Sound %u but sound does not exist.", i, temp.SoundId);
77  }
78 
79  if (!GetLanguageDescByID(temp.Language))
80  sLog.outErrorDb("CreatureEventAI: Entry %i in table creature_ai_texts using Language %u but Language does not exist.", i, temp.Language);
81 
82  if (temp.Type > CHAT_TYPE_ZONE_YELL)
83  sLog.outErrorDb("CreatureEventAI: Entry %i in table creature_ai_texts has Type %u but this Chat Type does not exist.", i, temp.Type);
84 
85  if (temp.Emote)
86  {
87  if (!sEmotesStore.LookupEntry(temp.Emote))
88  sLog.outErrorDb("CreatureEventAI: Entry %i in table creature_ai_texts has Emote %u but emote does not exist.", i, temp.Emote);
89  }
90 
91  m_CreatureEventAI_TextMap[i] = temp;
92  ++count;
93  }
94  while (result->NextRow());
95 
96  if (check_entry_use)
98 
99  sLog.outString(">> Loaded %u additional CreatureEventAI Texts data.", count);
100  }
101  else
102  sLog.outString(">> Loaded 0 additional CreatureEventAI Texts data. DB table creature_ai_texts is empty.");
103 
104 }
105 
107 {
108  std::set<int32> idx_set;
109  // check not used strings this is negative range
110  for (CreatureEventAI_TextMap::const_iterator itr = m_CreatureEventAI_TextMap.begin(); itr != m_CreatureEventAI_TextMap.end(); ++itr)
111  idx_set.insert(itr->first);
112 
113  for (CreatureEventAI_Event_Map::const_iterator itr = m_CreatureEventAI_Event_Map.begin(); itr != m_CreatureEventAI_Event_Map.end(); ++itr)
114  {
115  for (size_t i = 0; i < itr->second.size(); ++i)
116  {
117  CreatureEventAI_Event const& event = itr->second[i];
118 
119  for (int j = 0; j < MAX_ACTIONS; ++j)
120  {
121  CreatureEventAI_Action const& action = event.action[j];
122  switch (action.type)
123  {
124  case ACTION_T_TEXT:
126  {
127  // ACTION_T_CHANCED_TEXT contains a chance value in first param
128  int k = action.type == ACTION_T_TEXT ? 0 : 1;
129  for (; k < 3; ++k)
130  if (action.text.TextId[k])
131  idx_set.erase(action.text.TextId[k]);
132  break;
133  }
134  default:
135  break;
136  }
137  }
138  }
139  }
140 
141  for (std::set<int32>::const_iterator itr = idx_set.begin(); itr != idx_set.end(); ++itr)
142  sLog.outErrorDb("CreatureEventAI: Entry %i in table creature_ai_texts but not used in EventAI scripts.", *itr);
143 }
144 
145 // -------------------
147 {
148 
149  //Drop Existing EventSummon Map
151 
152  // Gather additional data for EventAI
153  QueryResult_AutoPtr result = WorldDatabase.Query("SELECT id, position_x, position_y, position_z, orientation, spawntimesecs FROM creature_ai_summons");
154  if (result)
155  {
156  uint32 Count = 0;
157 
158  do
159  {
160  Field* fields = result->Fetch();
161 
163 
164  uint32 i = fields[0].GetUInt32();
165  temp.position_x = fields[1].GetFloat();
166  temp.position_y = fields[2].GetFloat();
167  temp.position_z = fields[3].GetFloat();
168  temp.orientation = fields[4].GetFloat();
169  temp.SpawnTimeSecs = fields[5].GetUInt32();
170 
172  {
173  sLog.outErrorDb("CreatureEventAI: Summon id %u has invalid coordinates (%f,%f,%f,%f), skipping.", i, temp.position_x, temp.position_y, temp.position_z, temp.orientation);
174  continue;
175  }
176 
177  //Add to map
179  ++Count;
180  }
181  while (result->NextRow());
182 
183  if (check_entry_use)
185 
186  sLog.outString(">> Loaded %u CreatureEventAI summon definitions", Count);
187  }
188  else
189  sLog.outString(">> Loaded 0 CreatureEventAI Summon definitions. DB table creature_ai_summons is empty.");
190 
191 }
192 
194 {
195  std::set<int32> idx_set;
196  // check not used strings this is negative range
197  for (CreatureEventAI_Summon_Map::const_iterator itr = m_CreatureEventAI_Summon_Map.begin(); itr != m_CreatureEventAI_Summon_Map.end(); ++itr)
198  idx_set.insert(itr->first);
199 
200  for (CreatureEventAI_Event_Map::const_iterator itr = m_CreatureEventAI_Event_Map.begin(); itr != m_CreatureEventAI_Event_Map.end(); ++itr)
201  {
202  for (size_t i = 0; i < itr->second.size(); ++i)
203  {
204  CreatureEventAI_Event const& event = itr->second[i];
205  for (int j = 0; j < MAX_ACTIONS; ++j)
206  {
207  CreatureEventAI_Action const& action = event.action[j];
208  switch (action.type)
209  {
210  case ACTION_T_SUMMON_ID:
211  {
212  if (action.summon_id.spawnId)
213  idx_set.erase(action.summon_id.spawnId);
214  break;
215  }
216  default:
217  break;
218  }
219  }
220  }
221  }
222 
223  for (std::set<int32>::const_iterator itr = idx_set.begin(); itr != idx_set.end(); ++itr)
224  sLog.outErrorDb("CreatureEventAI: Entry %i in table creature_ai_summons but not used in EventAI scripts.", *itr);
225 }
226 
227 // -------------------
229 {
230  //Drop Existing EventAI List
232 
233  // Gather event data
234  QueryResult_AutoPtr result = WorldDatabase.Query("SELECT id, creature_id, event_type, event_inverse_phase_mask, event_chance, event_flags, "
235  "event_param1, event_param2, event_param3, event_param4, "
236  "action1_type, action1_param1, action1_param2, action1_param3, "
237  "action2_type, action2_param1, action2_param2, action2_param3, "
238  "action3_type, action3_param1, action3_param2, action3_param3 "
239  "FROM creature_ai_scripts");
240  if (result)
241  {
242  uint32 Count = 0;
243 
244  do
245  {
246  Field* fields = result->Fetch();
247 
249  temp.event_id = EventAI_Type(fields[0].GetUInt32());
250  uint32 i = temp.event_id;
251 
252  temp.creature_id = fields[1].GetUInt32();
253  uint32 creature_id = temp.creature_id;
254 
255  uint32 e_type = fields[2].GetUInt32();
256  //Report any errors in event
257  if (e_type >= EVENT_T_END)
258  {
259  sLog.outErrorDb("CreatureEventAI: Event %u has incorrect type (%u), skipping.", i, e_type);
260  continue;
261  }
262  temp.event_type = EventAI_Type(e_type);
263 
264  temp.event_inverse_phase_mask = fields[3].GetUInt32();
265  temp.event_chance = fields[4].GetUInt8();
266  temp.event_flags = fields[5].GetUInt8();
267  temp.raw.param1 = fields[6].GetUInt32();
268  temp.raw.param2 = fields[7].GetUInt32();
269  temp.raw.param3 = fields[8].GetUInt32();
270  temp.raw.param4 = fields[9].GetUInt32();
271 
272  //Creature does not exist in database
274  {
275  sLog.outErrorDb("CreatureEventAI: Event %u has script for invalid creature entry (%u), skipping.", i, temp.creature_id);
276  continue;
277  }
278 
280  {
281  if (!cInfo->AIName || !cInfo->AIName[0])
282  {
283  sLog.outErrorDb("CreatureEventAI: Creature Entry %u has EventAI script but its AIName is empty. Set to EventAI as default.", cInfo->Entry);
284  delete[] const_cast<char*>(cInfo->AIName);
285  const_cast<CreatureInfo*>(cInfo)->AIName = new char[sizeof("EventAI")];
286  memcpy(const_cast<char*>(cInfo->AIName), "EventAI", sizeof("EventAI"));
287  }
288  if (strcmp(cInfo->AIName, "EventAI"))
289  sLog.outErrorDb("CreatureEventAI: Creature Entry %u has EventAI script but it has AIName %s. EventAI script will be overriden.", cInfo->Entry, cInfo->AIName);
290  if (cInfo->ScriptID)
291  sLog.outErrorDb("CreatureEventAI: Creature Entry %u has EventAI script but it also has C++ script. EventAI script will be overriden.", cInfo->Entry);
292  }
293 
294  //No chance of this event occuring
295  if (temp.event_chance == 0)
296  sLog.outErrorDb("CreatureEventAI: Event %u has 0 percent chance. Event will never trigger!", i);
297  //Chance above 100, force it to be 100
298  else if (temp.event_chance > 100)
299  {
300  sLog.outErrorDb("CreatureEventAI: Creature %u is using event %u with more than 100 percent chance. Adjusting to 100 percent.", temp.creature_id, i);
301  temp.event_chance = 100;
302  }
303 
304  //Individual event checks
305  switch (temp.event_type)
306  {
307  case EVENT_T_TIMER:
308  case EVENT_T_TIMER_OOC:
309  if (temp.timer.initialMax < temp.timer.initialMin)
310  sLog.outErrorDb("CreatureEventAI: Creature %u is using timed event(%u) with param2 < param1 (InitialMax < InitialMin). Event will never repeat.", temp.creature_id, i);
311  if (temp.timer.repeatMax < temp.timer.repeatMin)
312  sLog.outErrorDb("CreatureEventAI: Creature %u is using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
313  break;
314  case EVENT_T_HP:
315  case EVENT_T_MANA:
316  case EVENT_T_TARGET_HP:
317  case EVENT_T_TARGET_MANA:
318  if (temp.percent_range.percentMax > 100)
319  sLog.outErrorDb("CreatureEventAI: Creature %u is using percentage event(%u) with param2 (MinPercent) > 100. Event will never trigger! ", temp.creature_id, i);
320 
321  if (temp.percent_range.percentMax <= temp.percent_range.percentMin)
322  sLog.outErrorDb("CreatureEventAI: Creature %u is using percentage event(%u) with param1 <= param2 (MaxPercent <= MinPercent). Event will never trigger! ", temp.creature_id, i);
323 
324  if (temp.event_flags & EFLAG_REPEATABLE && !temp.percent_range.repeatMin && !temp.percent_range.repeatMax)
325  {
326  sLog.outErrorDb("CreatureEventAI: Creature %u has param3 and param4=0 (RepeatMin/RepeatMax) but cannot be repeatable without timers. Removing EFLAG_REPEATABLE for event %u.", temp.creature_id, i);
327  temp.event_flags &= ~EFLAG_REPEATABLE;
328  }
329  break;
330  case EVENT_T_SPELLHIT:
331  if (temp.spell_hit.spellId)
332  {
333  SpellEntry const* pSpell = sSpellStore.LookupEntry(temp.spell_hit.spellId);
334  if (!pSpell)
335  {
336  sLog.outErrorDb("CreatureEventAI: Creature %u has non-existant SpellID(%u) defined in event %u.", temp.creature_id, temp.spell_hit.spellId, i);
337  continue;
338  }
339 
340  if ((temp.spell_hit.schoolMask & pSpell->SchoolMask) != pSpell->SchoolMask)
341  sLog.outErrorDb("CreatureEventAI: Creature %u has param1(spellId %u) but param2 is not -1 and not equal to spell's school mask. Event %u can never trigger.", temp.creature_id, temp.spell_hit.schoolMask, i);
342  }
343 
344  if (!temp.spell_hit.schoolMask)
345  sLog.outErrorDb("CreatureEventAI: Creature %u is using invalid SpellSchoolMask(%u) defined in event %u.", temp.creature_id, temp.spell_hit.schoolMask, i);
346 
347  if (temp.spell_hit.repeatMax < temp.spell_hit.repeatMin)
348  sLog.outErrorDb("CreatureEventAI: Creature %u is using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
349  break;
350  case EVENT_T_RANGE:
351  if (temp.range.maxDist < temp.range.minDist)
352  sLog.outErrorDb("CreatureEventAI: Creature %u is using event(%u) with param2 < param1 (MaxDist < MinDist). Event will never repeat.", temp.creature_id, i);
353  if (temp.range.repeatMax < temp.range.repeatMin)
354  sLog.outErrorDb("CreatureEventAI: Creature %u is using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
355  break;
356  case EVENT_T_OOC_LOS:
357  if (temp.ooc_los.repeatMax < temp.ooc_los.repeatMin)
358  sLog.outErrorDb("CreatureEventAI: Creature %u is using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
359  break;
360  case EVENT_T_SPAWNED:
361  switch (temp.spawned.condition)
362  {
363  case SPAWNED_EVENT_ALWAY:
364  break;
365  case SPAWNED_EVENT_MAP:
366  if (!sMapStore.LookupEntry(temp.spawned.conditionValue1))
367  sLog.outErrorDb("CreatureEventAI: Creature %u is using spawned event(%u) with param1 = %u 'map specific' with invalid map (%u) in param2. Event will never repeat.", temp.creature_id, i, temp.spawned.condition, temp.spawned.conditionValue1);
368  break;
369  case SPAWNED_EVENT_ZONE:
370  if (!GetAreaEntryByAreaID(temp.spawned.conditionValue1))
371  sLog.outErrorDb("CreatureEventAI: Creature %u is using spawned event(%u) with param1 = %u 'area specific' with invalid area (%u) in param2. Event will never repeat.", temp.creature_id, i, temp.spawned.condition, temp.spawned.conditionValue1);
372  default:
373  sLog.outErrorDb("CreatureEventAI: Creature %u is using invalid spawned event %u mode (%u) in param1", temp.creature_id, i, temp.spawned.condition);
374  break;
375  }
376  break;
377  case EVENT_T_FRIENDLY_HP:
378  if (temp.friendly_hp.repeatMax < temp.friendly_hp.repeatMin)
379  sLog.outErrorDb("CreatureEventAI: Creature %u is using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
380  break;
382  if (temp.friendly_is_cc.repeatMax < temp.friendly_is_cc.repeatMin)
383  sLog.outErrorDb("CreatureEventAI: Creature %u is using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
384  break;
386  {
387  SpellEntry const* pSpell = sSpellStore.LookupEntry(temp.spell_hit.spellId);
388  if (!pSpell)
389  {
390  sLog.outErrorDb("CreatureEventAI: Creature %u has non-existant SpellID(%u) defined in event %u.", temp.creature_id, temp.spell_hit.spellId, i);
391  continue;
392  }
393  if (temp.friendly_buff.repeatMax < temp.friendly_buff.repeatMin)
394  sLog.outErrorDb("CreatureEventAI: Creature %u is using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
395  break;
396  }
397  case EVENT_T_KILL:
398  if (temp.kill.repeatMax < temp.kill.repeatMin)
399  sLog.outErrorDb("CreatureEventAI: Creature %u is using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
400  break;
402  if (temp.target_casting.repeatMax < temp.target_casting.repeatMin)
403  sLog.outErrorDb("CreatureEventAI: Creature %u is using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
404  break;
408  if (!sCreatureStorage.LookupEntry<CreatureInfo>(temp.summon_unit.creatureId))
409  sLog.outErrorDb("CreatureEventAI: Creature %u is using event(%u) with invalid creature template id (%u) in param1, skipped.", temp.creature_id, i, temp.summon_unit.creatureId);
410  if (temp.summon_unit.repeatMax < temp.summon_unit.repeatMin)
411  sLog.outErrorDb("CreatureEventAI: Creature %u is using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
412  break;
415  if (!sObjectMgr.GetQuestTemplate(temp.quest.questId))
416  sLog.outErrorDb("CreatureEventAI: Creature %u is using event(%u) with invalid quest id (%u) in param1, skipped.", temp.creature_id, i, temp.quest.questId);
417  sLog.outErrorDb("CreatureEventAI: Creature %u using not implemented event (%u) in event %u.", temp.creature_id, temp.event_id, i);
418  continue;
419 
420  case EVENT_T_AGGRO:
421  case EVENT_T_DEATH:
422  case EVENT_T_EVADE:
424  {
425  if (temp.event_flags & EFLAG_REPEATABLE)
426  {
427  sLog.outErrorDb("CreatureEventAI: Creature %u has EFLAG_REPEATABLE set. Event can never be repeatable. Removing flag for event %u.", temp.creature_id, i);
428  temp.event_flags &= ~EFLAG_REPEATABLE;
429  }
430 
431  break;
432  }
433 
435  {
436  if (!sEmotesTextStore.LookupEntry(temp.receive_emote.emoteId))
437  {
438  sLog.outErrorDb("CreatureEventAI: Creature %u using event %u: param1 (EmoteTextId: %u). EmoteTextId is not valid.", temp.creature_id, i, temp.receive_emote.emoteId);
439  continue;
440  }
441 
442  if (temp.receive_emote.condition)
443  {
444  Condition cond;
445  cond.Type = ConditionType(temp.receive_emote.condition);
446  cond.ConditionValue1 = temp.receive_emote.conditionValue1;
447  cond.ConditionValue2 = temp.receive_emote.conditionValue2;
448  if (!sConditionMgr.isConditionTypeValid(&cond))
449  {
450  sLog.outErrorDb("CreatureEventAI: Creature %u using event %u: param2 (Condition: %u) are not valid.",temp.creature_id, i, temp.receive_emote.condition);
451  continue;
452  }
453  }
454 
455  if (!(temp.event_flags & EFLAG_REPEATABLE))
456  {
457  sLog.outErrorDb("CreatureEventAI: Creature %u using event %u: EFLAG_REPEATABLE not set. Event must always be repeatable. Flag applied.", temp.creature_id, i);
459  }
460 
461  break;
462  }
463 
464  case EVENT_T_BUFFED:
468  {
469  SpellEntry const* pSpell = sSpellStore.LookupEntry(temp.buffed.spellId);
470  if (!pSpell)
471  {
472  sLog.outErrorDb("CreatureEventAI: Creature %u has non-existant SpellID(%u) defined in event %u.", temp.creature_id, temp.spell_hit.spellId, i);
473  continue;
474  }
475  if (temp.buffed.repeatMax < temp.buffed.repeatMin)
476  sLog.outErrorDb("CreatureEventAI: Creature %u is using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
477  break;
478  }
479 
480  default:
481  sLog.outErrorDb("CreatureEventAI: Creature %u using not checked at load event (%u) in event %u. Need check code update?", temp.creature_id, temp.event_id, i);
482  break;
483  }
484 
485  for (uint32 j = 0; j < MAX_ACTIONS; j++)
486  {
487  uint16 action_type = fields[10 + (j * 4)].GetUInt16();
488  if (action_type >= ACTION_T_END)
489  {
490  sLog.outErrorDb("CreatureEventAI: Event %u Action %u has incorrect action type (%u), replace by ACTION_T_NONE.", i, j + 1, action_type);
491  temp.action[j].type = ACTION_T_NONE;
492  continue;
493  }
494 
495  CreatureEventAI_Action& action = temp.action[j];
496 
497  action.type = EventAI_ActionType(action_type);
498  action.raw.param1 = fields[11 + (j * 4)].GetUInt32();
499  action.raw.param2 = fields[12 + (j * 4)].GetUInt32();
500  action.raw.param3 = fields[13 + (j * 4)].GetUInt32();
501 
502  //Report any errors in actions
503  switch (action.type)
504  {
505  case ACTION_T_NONE:
506  break;
508  // Check first param as chance
509  if (!action.chanced_text.chance)
510  sLog.outErrorDb("CreatureEventAI: Event %u Action %u has not set chance param1. Text will not be displayed", i, j + 1);
511  else if (action.chanced_text.chance >= 100)
512  sLog.outErrorDb("CreatureEventAI: Event %u Action %u has set chance param1 >= 100. Text will always be displayed", i, j + 1);
513  // no break here to check texts
514  case ACTION_T_TEXT:
515  {
516  bool not_set = false;
517  int firstTextParam = action.type == ACTION_T_TEXT ? 0 : 1;
518  for (int k = firstTextParam; k < 3; ++k)
519  {
520  if (action.text.TextId[k])
521  {
522  if (k > firstTextParam && not_set)
523  sLog.outErrorDb("CreatureEventAI: Event %u Action %u has param%d, but it follow after not set param. Required for randomized text.", i, j + 1, k + 1);
524  if (!action.text.TextId[k])
525  not_set = true;
526 
527  // range negative
528  else if (action.text.TextId[k] > MIN_CREATURE_AI_TEXT_STRING_ID || action.text.TextId[k] <= MAX_CREATURE_AI_TEXT_STRING_ID)
529  {
530  sLog.outErrorDb("CreatureEventAI: Event %u Action %u param%d references out-of-range entry (%i) in texts table.", i, j + 1, k + 1, action.text.TextId[k]);
531  action.text.TextId[k] = 0;
532  }
533  else if (m_CreatureEventAI_TextMap.find(action.text.TextId[k]) == m_CreatureEventAI_TextMap.end())
534  {
535  sLog.outErrorDb("CreatureEventAI: Event %u Action %u param%d references invalid entry (%i) in texts table.", i, j + 1, k + 1, action.text.TextId[k]);
536  action.text.TextId[k] = 0;
537  }
538  }
539  }
540  break;
541  }
543  if (action.set_faction.factionId != 0 && !sFactionStore.LookupEntry(action.set_faction.factionId))
544  {
545  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent FactionId %u.", i, j + 1, action.set_faction.factionId);
546  action.set_faction.factionId = 0;
547  }
548  break;
550  if (action.morph.creatureId != 0 || action.morph.modelId != 0)
551  {
552  if (action.morph.creatureId && !sCreatureStorage.LookupEntry<CreatureInfo>(action.morph.creatureId))
553  {
554  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant Creature entry %u.", i, j + 1, action.morph.creatureId);
555  action.morph.creatureId = 0;
556  }
557 
558  if (action.morph.modelId)
559  {
560  if (action.morph.creatureId)
561  {
562  sLog.outErrorDb("CreatureEventAI: Event %u Action %u has unused ModelId %u for creature id %u.", i, j + 1, action.morph.modelId, action.morph.creatureId);
563  action.morph.modelId = 0;
564  }
565  else if (!sCreatureDisplayInfoStore.LookupEntry(action.morph.modelId))
566  {
567  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant ModelId %u.", i, j + 1, action.morph.modelId);
568  action.morph.modelId = 0;
569  }
570  }
571  }
572  break;
573  case ACTION_T_SOUND:
574  if (!sSoundEntriesStore.LookupEntry(action.sound.soundId))
575  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses invalid SoundID %u.", i, j + 1, action.sound.soundId);
576  break;
577  case ACTION_T_EMOTE:
578  if (!sEmotesStore.LookupEntry(action.emote.emoteId))
579  sLog.outErrorDb("CreatureEventAI: Event %u Action %u param1 (EmoteId: %u): EmoteId is not valid.", i, j + 1, action.emote.emoteId);
580  break;
582  if (!sSoundEntriesStore.LookupEntry(action.random_sound.soundId1))
583  sLog.outErrorDb("CreatureEventAI: Event %u Action %u param1 uses invalid SoundID %u.", i, j + 1, action.random_sound.soundId1);
584  if (action.random_sound.soundId2 >= 0 && !sSoundEntriesStore.LookupEntry(action.random_sound.soundId2))
585  sLog.outErrorDb("CreatureEventAI: Event %u Action %u param2 uses invalid SoundID %u.", i, j + 1, action.random_sound.soundId2);
586  if (action.random_sound.soundId3 >= 0 && !sSoundEntriesStore.LookupEntry(action.random_sound.soundId3))
587  sLog.outErrorDb("CreatureEventAI: Event %u Action %u param3 uses invalid SoundID %u.", i, j + 1, action.random_sound.soundId3);
588  break;
590  if (!sEmotesStore.LookupEntry(action.random_emote.emoteId1))
591  sLog.outErrorDb("CreatureEventAI: Event %u Action %u param1 (EmoteId: %u): EmoteId is not valid.", i, j + 1, action.random_emote.emoteId1);
592  if (action.random_emote.emoteId2 >= 0 && !sEmotesStore.LookupEntry(action.random_emote.emoteId2))
593  sLog.outErrorDb("CreatureEventAI: Event %u Action %u param2 (EmoteId: %u): EmoteId is not valid.", i, j + 1, action.random_emote.emoteId2);
594  if (action.random_emote.emoteId3 >= 0 && !sEmotesStore.LookupEntry(action.random_emote.emoteId3))
595  sLog.outErrorDb("CreatureEventAI: Event %u Action %u param3 (EmoteId: %u): EmoteId is not valid.", i, j + 1, action.random_emote.emoteId3);
596  break;
597  case ACTION_T_CAST:
598  {
599  const SpellEntry* spell = sSpellStore.LookupEntry(action.cast.spellId);
600  if (!spell)
601  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses invalid SpellID %u.", i, j + 1, action.cast.spellId);
602  /* FIXME: temp.raw.param3 not have event tipes with recovery time in it....
603  else
604  {
605  if (spell->RecoveryTime > 0 && temp.event_flags & EFLAG_REPEATABLE)
606  {
607  //output as debug for now, also because there's no general rule all spells have RecoveryTime
608  if (temp.event_param3 < spell->RecoveryTime)
609  sLog.outDebug("CreatureEventAI: Event %u Action %u uses SpellID %u but cooldown is longer(%u) than minumum defined in event param3(%u).", i, j+1,action.cast.spellId, spell->RecoveryTime, temp.event_param3);
610  }
611  }
612  */
613 
614  //Cast is always triggered if target is forced to cast on self
615  if (action.cast.castFlags & CAST_FORCE_TARGET_SELF)
616  action.cast.castFlags |= CAST_TRIGGERED;
617 
618  if (action.cast.target >= TARGET_T_END)
619  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1);
620  break;
621  }
622  case ACTION_T_SUMMON:
623  if (!sCreatureStorage.LookupEntry<CreatureInfo>(action.summon.creatureId))
624  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses invalid creature entry %u.", i, j + 1, action.summon.creatureId);
625 
626  if (action.summon.target >= TARGET_T_END)
627  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1);
628  break;
630  if (std::abs(action.threat_single_pct.percent) > 100)
631  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses invalid percent value %u.", i, j + 1, action.threat_single_pct.percent);
632  if (action.threat_single_pct.target >= TARGET_T_END)
633  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1);
634  break;
636  if (std::abs(action.threat_all_pct.percent) > 100)
637  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses invalid percent value %u.", i, j + 1, action.threat_all_pct.percent);
638  break;
640  if (Quest const* qid = sObjectMgr.GetQuestTemplate(action.quest_event.questId))
641  {
642  if (!qid->HasSpecialFlag(QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT))
643  sLog.outErrorDb("CreatureEventAI: Event %u Action %u. SpecialFlags for quest entry %u does not include |2, Action will not have any effect.", i, j + 1, action.quest_event.questId);
644  }
645  else
646  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses invalid Quest entry %u.", i, j + 1, action.quest_event.questId);
647 
648  if (action.quest_event.target >= TARGET_T_END)
649  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1);
650 
651  break;
652  case ACTION_T_CAST_EVENT:
653  if (!sCreatureStorage.LookupEntry<CreatureInfo>(action.cast_event.creatureId))
654  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses invalid creature entry %u.", i, j + 1, action.cast_event.creatureId);
655  if (!sSpellStore.LookupEntry(action.cast_event.spellId))
656  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses invalid SpellID %u.", i, j + 1, action.cast_event.spellId);
657  if (action.cast_event.target >= TARGET_T_END)
658  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1);
659  break;
661  if (action.set_unit_field.field < OBJECT_END || action.set_unit_field.field >= UNIT_END)
662  sLog.outErrorDb("CreatureEventAI: Event %u Action %u param1 (UNIT_FIELD*). Index out of range for intended use.", i, j + 1);
663  if (action.set_unit_field.target >= TARGET_T_END)
664  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1);
665  break;
668  if (action.unit_flag.target >= TARGET_T_END)
669  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1);
670  break;
671  case ACTION_T_SET_PHASE:
672  if (action.set_phase.phase >= MAX_PHASE)
673  sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phase >= %u. Phase mask cannot be used past phase %u.", i, j + 1, MAX_PHASE, MAX_PHASE - 1);
674  break;
675  case ACTION_T_INC_PHASE:
676  if (action.set_inc_phase.step == 0)
677  sLog.outErrorDb("CreatureEventAI: Event %u Action %u is incrementing phase by 0. Was this intended?", i, j + 1);
678  else if (std::abs(action.set_inc_phase.step) > MAX_PHASE - 1)
679  sLog.outErrorDb("CreatureEventAI: Event %u Action %u phase (%i) exceeds maximum phase.", i, j + 1, action.set_inc_phase.step);
680  break;
682  if (Quest const* qid = sObjectMgr.GetQuestTemplate(action.quest_event_all.questId))
683  {
684  if (!qid->HasSpecialFlag(QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT))
685  sLog.outErrorDb("CreatureEventAI: Event %u Action %u. SpecialFlags for quest entry %u does not include |2, Action will not have any effect.", i, j + 1, action.quest_event_all.questId);
686  }
687  else
688  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent Quest entry %u.", i, j + 1, action.quest_event_all.questId);
689  break;
691  if (!sCreatureStorage.LookupEntry<CreatureInfo>(action.cast_event_all.creatureId))
692  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent creature entry %u.", i, j + 1, action.cast_event_all.creatureId);
693  if (!sSpellStore.LookupEntry(action.cast_event_all.spellId))
694  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent SpellID %u.", i, j + 1, action.cast_event_all.spellId);
695  break;
697  if (!sSpellStore.LookupEntry(action.remove_aura.spellId))
698  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent SpellID %u.", i, j + 1, action.remove_aura.spellId);
699  if (action.remove_aura.target >= TARGET_T_END)
700  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1);
701  break;
702  case ACTION_T_RANDOM_PHASE: //PhaseId1, PhaseId2, PhaseId3
703  if (action.random_phase.phase1 >= MAX_PHASE)
704  sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phase1 >= %u. Phase mask cannot be used past phase %u.", i, j + 1, MAX_PHASE, MAX_PHASE - 1);
705  if (action.random_phase.phase2 >= MAX_PHASE)
706  sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phase2 >= %u. Phase mask cannot be used past phase %u.", i, j + 1, MAX_PHASE, MAX_PHASE - 1);
707  if (action.random_phase.phase3 >= MAX_PHASE)
708  sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phase3 >= %u. Phase mask cannot be used past phase %u.", i, j + 1, MAX_PHASE, MAX_PHASE - 1);
709  break;
710  case ACTION_T_RANDOM_PHASE_RANGE: //PhaseMin, PhaseMax
711  if (action.random_phase_range.phaseMin >= MAX_PHASE)
712  sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phaseMin >= %u. Phase mask cannot be used past phase %u.", i, j + 1, MAX_PHASE, MAX_PHASE - 1);
713  if (action.random_phase_range.phaseMin >= MAX_PHASE)
714  sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phaseMax >= %u. Phase mask cannot be used past phase %u.", i, j + 1, MAX_PHASE, MAX_PHASE - 1);
715  if (action.random_phase_range.phaseMin >= action.random_phase_range.phaseMax)
716  {
717  sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phaseMax <= phaseMin.", i, j + 1);
718  std::swap(action.random_phase_range.phaseMin, action.random_phase_range.phaseMax);
719  // equal case processed at call
720  }
721  break;
722  case ACTION_T_SUMMON_ID:
723  if (!sCreatureStorage.LookupEntry<CreatureInfo>(action.summon_id.creatureId))
724  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant creature entry %u.", i, j + 1, action.summon_id.creatureId);
725  if (action.summon_id.target >= TARGET_T_END)
726  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1);
727  if (m_CreatureEventAI_Summon_Map.find(action.summon_id.spawnId) == m_CreatureEventAI_Summon_Map.end())
728  sLog.outErrorDb("CreatureEventAI: Event %u Action %u summons missing CreatureEventAI_Summon %u", i, j + 1, action.summon_id.spawnId);
729  break;
731  if (!sCreatureStorage.LookupEntry<CreatureInfo>(action.killed_monster.creatureId))
732  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant creature entry %u.", i, j + 1, action.killed_monster.creatureId);
733  if (action.killed_monster.target >= TARGET_T_END)
734  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1);
735  break;
737  //if (!(temp.event_flags & EFLAG_NORMAL) && !(temp.event_flags & EFLAG_HEROIC))
738  // sLog.outErrorDb("CreatureEventAI: Event %u Action %u. Cannot set instance data without event flags (normal/heroic).", i, j+1);
739  if (action.set_inst_data.value > 4/*SPECIAL*/)
740  sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set instance data above encounter state 4. Custom case?", i, j + 1);
741  break;
743  //if (!(temp.event_flags & EFLAG_NORMAL) && !(temp.event_flags & EFLAG_HEROIC))
744  // sLog.outErrorDb("CreatureEventAI: Event %u Action %u. Cannot set instance data without event flags (normal/heroic).", i, j+1);
745  if (action.set_inst_data64.target >= TARGET_T_END)
746  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j + 1);
747  break;
749  if (!sCreatureStorage.LookupEntry<CreatureInfo>(action.update_template.creatureId))
750  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant creature entry %u.", i, j + 1, action.update_template.creatureId);
751  break;
752  case ACTION_T_SET_SHEATH:
753  if (action.set_sheath.sheath >= MAX_SHEATH_STATE)
754  {
755  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses wrong sheath state %u.", i, j + 1, action.set_sheath.sheath);
756  action.set_sheath.sheath = SHEATH_STATE_UNARMED;
757  }
758  break;
760  if (action.invincibility_hp_level.is_percent)
761  {
762  if (action.invincibility_hp_level.hp_level > 100)
763  {
764  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses wrong percent value %u.", i, j + 1, action.invincibility_hp_level.hp_level);
765  action.invincibility_hp_level.hp_level = 100;
766  }
767  }
768  break;
770  if (action.mount.creatureId != 0 || action.mount.modelId != 0)
771  {
772  if (action.mount.creatureId && !sCreatureStorage.LookupEntry<CreatureInfo>(action.mount.creatureId))
773  {
774  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent Creature entry %u.", i, j + 1, action.mount.creatureId);
775  action.morph.creatureId = 0;
776  }
777 
778  if (action.mount.modelId)
779  {
780  if (action.mount.creatureId)
781  {
782  sLog.outErrorDb("CreatureEventAI: Event %u Action %u have unused ModelId %u with also set creature id %u.", i, j + 1, action.mount.modelId, action.mount.creatureId);
783  action.mount.modelId = 0;
784  }
785  else if (!sCreatureDisplayInfoStore.LookupEntry(action.mount.modelId))
786  {
787  sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses nonexistent ModelId %u.", i, j + 1, action.mount.modelId);
788  action.mount.modelId = 0;
789  }
790  }
791  }
792  break;
793  case ACTION_T_EVADE: //No Params
794  case ACTION_T_FLEE_FOR_ASSIST: //No Params
795  case ACTION_T_DIE: //No Params
796  case ACTION_T_ZONE_COMBAT_PULSE: //No Params
797  case ACTION_T_FORCE_DESPAWN: //No Params
798  case ACTION_T_AUTO_ATTACK: //AllowAttackState (0 = stop attack, anything else means continue attacking)
799  case ACTION_T_COMBAT_MOVEMENT: //AllowCombatMovement (0 = stop combat based movement, anything else continue attacking)
800  case ACTION_T_RANGED_MOVEMENT: //Distance, Angle
801  case ACTION_T_CALL_FOR_HELP: //Distance
802  break;
803 
804  case ACTION_T_RANDOM_SAY:
807  sLog.outErrorDb("CreatureEventAI: Event %u Action %u currently unused ACTION type. Did you forget to update database?", i, j + 1);
808  break;
809 
812  case ACTION_T_SUMMON_GO:
813  break;
814 
815  default:
816  sLog.outErrorDb("CreatureEventAI: Event %u Action %u has unchecked action type (%u). Need load check code update?", i, j + 1, temp.action[j].type);
817  break;
818  }
819  }
820 
821  //Add to list
822  m_CreatureEventAI_Event_Map[creature_id].push_back(temp);
823  ++Count;
824 
825  }
826  while (result->NextRow());
827 
830 
831  sLog.outString(">> Loaded %u CreatureEventAI scripts", Count);
832  }
833  else
834  sLog.outString(">> Loaded 0 CreatureEventAI scripts. DB table creature_ai_scripts is empty.");
835 }
836 
AreaTableEntry const * GetAreaEntryByAreaID(uint32 area_id)
Definition: DBCStores.cpp:623
struct CreatureEventAI_Event::@41::@58 raw
struct CreatureEventAI_Action::@3::@34 update_template
SQLStorage sCreatureStorage
ConditionType Type
Definition: ConditionMgr.h:186
struct CreatureEventAI_Action::@3::@12 cast
struct CreatureEventAI_Action::@3::@17 cast_event
struct CreatureEventAI_Action::@3::@31 killed_monster
void LoadCreatureEventAI_Summons(bool check_entry_use)
DatabaseType WorldDatabase
Accessor to the world database.
Definition: Main.cpp:53
struct CreatureEventAI_Action::@3::@37 invincibility_hp_level
DBCStorage< EmotesEntry > sEmotesStore(EmotesEntryfmt)
struct CreatureEventAI_Event::@41::@47 range
struct CreatureEventAI_Event::@41::@56 receive_emote
struct CreatureEventAI_Action::@3::@23 set_inc_phase
Definition: Field.h:24
struct CreatureEventAI_Event::@41::@54 summon_unit
struct CreatureEventAI_Action::@3::@22 set_phase
EventAI_ActionType type
#define MAX_ACTIONS
#define sLog
Log class singleton.
Definition: Log.h:187
ACE_INT32 int32
Definition: Define.h:67
struct CreatureEventAI_Action::@3::@13 summon
QueryResult_AutoPtr Query(const char *sql)
Definition: Database.cpp:383
bool IsValidMapCoord(float c)
Definition: GridDefines.h:192
struct CreatureEventAI_Action::@3::@7 morph
EventAI_ActionType
struct CreatureEventAI_Event::@41::@43 timer
#define sObjectMgr
Definition: ObjectMgr.h:1285
ConditionType
Definition: ConditionMgr.h:29
DBCStorage< MapEntry > sMapStore(MapEntryfmt)
DBCStorage< SpellEntry > sSpellStore(SpellEntryfmt)
DBCStorage< CreatureDisplayInfoEntry > sCreatureDisplayInfoStore(CreatureDisplayInfofmt)
struct CreatureEventAI_Action::@3::@30 summon_id
struct CreatureEventAI_Action::@3::@40 raw
#define MAX_CREATURE_AI_TEXT_STRING_ID
Definition: ObjectMgr.h:371
struct CreatureEventAI_Action::@3::@11 random_emote
CreatureEventAI_Action action[MAX_ACTIONS]
struct CreatureEventAI_Event::@41::@51 friendly_hp
struct CreatureEventAI_Action::@3::@38 mount
#define MAX_SHEATH_STATE
Definition: Unit.h:227
struct CreatureEventAI_Action::@3::@5 text
struct CreatureEventAI_Event::@41::@48 ooc_los
struct CreatureEventAI_Event::@41::@55 quest
struct CreatureEventAI_Event::@41::@45 kill
struct CreatureEventAI_Event::@41::@44 percent_range
size_t Count(const ContainerMapList< SPECIFIC_TYPE > &elements, SPECIFIC_TYPE *)
CreatureEventAI_Summon_Map m_CreatureEventAI_Summon_Map
struct CreatureEventAI_Event::@41::@57 buffed
#define sConditionMgr
Definition: ConditionMgr.h:312
uint32 SchoolMask
Definition: DBCStructure.h:772
struct CreatureEventAI_Action::@3::@9 emote
struct CreatureEventAI_Action::@3::@16 quest_event
CreatureEventAI_Event_Map m_CreatureEventAI_Event_Map
struct CreatureEventAI_Event::@41::@49 spawned
struct CreatureEventAI_Event::@41::@50 target_casting
struct CreatureEventAI_Action::@3::@19 unit_flag
struct CreatureEventAI_Action::@3::@6 set_faction
DBCStorage< SoundEntriesEntry > sSoundEntriesStore(SoundEntriesfmt)
uint32 ConditionValue2
Definition: ConditionMgr.h:188
struct CreatureEventAI_Action::@3::@14 threat_single_pct
LanguageDesc const * GetLanguageDescByID(uint32 lang)
Definition: ObjectMgr.cpp:269
T const * LookupEntry(uint32 id) const
Definition: SQLStorage.h:52
void LoadCreatureEventAI_Texts(bool check_entry_use)
#define MIN_CREATURE_AI_TEXT_STRING_ID
Definition: ObjectMgr.h:370
struct CreatureEventAI_Action::@3::@32 set_inst_data
struct CreatureEventAI_Action::@3::@25 cast_event_all
CreatureEventAI_TextMap m_CreatureEventAI_TextMap
struct CreatureEventAI_Action::@3::@15 threat_all_pct
struct CreatureEventAI_Action::@3::@26 remove_aura
ACE_Refcounted_Auto_Ptr< QueryResult, ACE_Null_Mutex > QueryResult_AutoPtr
Definition: QueryResult.h:113
uint32 ConditionValue1
Definition: ConditionMgr.h:187
struct CreatureEventAI_Action::@3::@10 random_sound
DBCStorage< FactionEntry > sFactionStore(FactionEntryfmt)
struct CreatureEventAI_Action::@3::@18 set_unit_field
struct CreatureEventAI_Action::@3::@39 chanced_text
struct CreatureEventAI_Event::@41::@53 friendly_buff
#define MAX_PHASE
struct CreatureEventAI_Event::@41::@46 spell_hit
struct CreatureEventAI_Event::@41::@52 friendly_is_cc
ACE_UINT16 uint16
Definition: Define.h:72
ACE_UINT32 uint32
Definition: Define.h:71
struct CreatureEventAI_Action::@3::@24 quest_event_all
DBCStorage< EmotesTextEntry > sEmotesTextStore(EmotesTextEntryfmt)
INSTANTIATE_SINGLETON_1(CreatureEventAIMgr)
struct CreatureEventAI_Action::@3::@33 set_inst_data64
EventAI_Type
struct CreatureEventAI_Action::@3::@28 random_phase
struct CreatureEventAI_Action::@3::@8 sound
struct CreatureEventAI_Action::@3::@29 random_phase_range
struct CreatureEventAI_Action::@3::@36 set_sheath