2 * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 #include "Database/DatabaseEnv.h"
21 #include "Database/SQLStorage.h"
22 #include "CreatureEventAI.h"
23 #include "CreatureEventAIMgr.h"
24 #include "ObjectMgr.h"
25 #include "ProgressBar.h"
26 #include "Policies/SingletonImp.h"
27 #include "ObjectGuid.h"
28 #include "GridDefines.h"
30 INSTANTIATE_SINGLETON_1(CreatureEventAIMgr
);
32 // -------------------
33 void CreatureEventAIMgr::LoadCreatureEventAI_Texts(bool check_entry_use
)
35 // Drop Existing Text Map, only done once and we are ready to add data from multiple sources.
36 m_CreatureEventAI_TextMap
.clear();
39 sObjectMgr
.LoadMangosStrings(WorldDatabase
,"creature_ai_texts",MIN_CREATURE_AI_TEXT_STRING_ID
,MAX_CREATURE_AI_TEXT_STRING_ID
);
41 // Gather Additional data from EventAI Texts
42 QueryResult
*result
= WorldDatabase
.Query("SELECT entry, sound, type, language, emote FROM creature_ai_texts");
44 sLog
.outString("Loading EventAI Texts additional data...");
47 barGoLink
bar((int)result
->GetRowCount());
53 Field
* fields
= result
->Fetch();
56 int32 i
= fields
[0].GetInt32();
57 temp
.SoundId
= fields
[1].GetInt32();
58 temp
.Type
= fields
[2].GetInt32();
59 temp
.Language
= fields
[3].GetInt32();
60 temp
.Emote
= fields
[4].GetInt32();
63 if (i
> MIN_CREATURE_AI_TEXT_STRING_ID
|| i
<= MAX_CREATURE_AI_TEXT_STRING_ID
)
65 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
);
69 // range negative (don't must be happen, loaded from same table)
70 if (!sObjectMgr
.GetMangosStringLocale(i
))
72 sLog
.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` not found",i
);
78 if (!sSoundEntriesStore
.LookupEntry(temp
.SoundId
))
79 sLog
.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` has Sound %u but sound does not exist.",i
,temp
.SoundId
);
82 if (!GetLanguageDescByID(temp
.Language
))
83 sLog
.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` using Language %u but Language does not exist.",i
,temp
.Language
);
85 if (temp
.Type
> CHAT_TYPE_ZONE_YELL
)
86 sLog
.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` has Type %u but this Chat Type does not exist.",i
,temp
.Type
);
90 if (!sEmotesStore
.LookupEntry(temp
.Emote
))
91 sLog
.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` has Emote %u but emote does not exist.",i
,temp
.Emote
);
94 m_CreatureEventAI_TextMap
[i
] = temp
;
96 } while (result
->NextRow());
101 CheckUnusedAITexts();
104 sLog
.outString(">> Loaded %u additional CreatureEventAI Texts data.", count
);
111 sLog
.outString(">> Loaded 0 additional CreatureEventAI Texts data. DB table `creature_ai_texts` is empty.");
115 void CreatureEventAIMgr::CheckUnusedAITexts()
117 std::set
<int32
> idx_set
;
118 // check not used strings this is negative range
119 for(CreatureEventAI_TextMap::const_iterator itr
= m_CreatureEventAI_TextMap
.begin(); itr
!= m_CreatureEventAI_TextMap
.end(); ++itr
)
120 idx_set
.insert(itr
->first
);
122 for(CreatureEventAI_Event_Map::const_iterator itr
= m_CreatureEventAI_Event_Map
.begin(); itr
!= m_CreatureEventAI_Event_Map
.end(); ++itr
)
124 for(size_t i
= 0; i
< itr
->second
.size(); ++i
)
126 CreatureEventAI_Event
const& event
= itr
->second
[i
];
128 for(int j
= 0; j
< MAX_ACTIONS
; ++j
)
130 CreatureEventAI_Action
const& action
= event
.action
[j
];
135 for(int k
= 0; k
< 3; ++k
)
136 if (action
.text
.TextId
[k
])
137 idx_set
.erase(action
.text
.TextId
[k
]);
147 for(std::set
<int32
>::const_iterator itr
= idx_set
.begin(); itr
!= idx_set
.end(); ++itr
)
148 sLog
.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` but not used in EventAI scripts.",*itr
);
151 // -------------------
152 void CreatureEventAIMgr::LoadCreatureEventAI_Summons(bool check_entry_use
)
155 //Drop Existing EventSummon Map
156 m_CreatureEventAI_Summon_Map
.clear();
158 // Gather additional data for EventAI
159 QueryResult
*result
= WorldDatabase
.Query("SELECT id, position_x, position_y, position_z, orientation, spawntimesecs FROM creature_ai_summons");
162 barGoLink
bar((int)result
->GetRowCount());
168 Field
*fields
= result
->Fetch();
170 CreatureEventAI_Summon temp
;
172 uint32 i
= fields
[0].GetUInt32();
173 temp
.position_x
= fields
[1].GetFloat();
174 temp
.position_y
= fields
[2].GetFloat();
175 temp
.position_z
= fields
[3].GetFloat();
176 temp
.orientation
= fields
[4].GetFloat();
177 temp
.SpawnTimeSecs
= fields
[5].GetUInt32();
179 if(!MaNGOS::IsValidMapCoord(temp
.position_x
,temp
.position_y
,temp
.position_z
,temp
.orientation
))
181 sLog
.outErrorDb("CreatureEventAI: Summon id %u have wrong coordinates (%f,%f,%f,%f), skipping.", i
,temp
.position_x
,temp
.position_y
,temp
.position_z
,temp
.orientation
);
186 m_CreatureEventAI_Summon_Map
[i
] = temp
;
188 }while (result
->NextRow());
193 CheckUnusedAISummons();
196 sLog
.outString(">> Loaded %u CreatureEventAI summon definitions", Count
);
202 sLog
.outString(">> Loaded 0 CreatureEventAI Summon definitions. DB table `creature_ai_summons` is empty.");
206 void CreatureEventAIMgr::CheckUnusedAISummons()
208 std::set
<int32
> idx_set
;
209 // check not used strings this is negative range
210 for(CreatureEventAI_Summon_Map::const_iterator itr
= m_CreatureEventAI_Summon_Map
.begin(); itr
!= m_CreatureEventAI_Summon_Map
.end(); ++itr
)
211 idx_set
.insert(itr
->first
);
213 for(CreatureEventAI_Event_Map::const_iterator itr
= m_CreatureEventAI_Event_Map
.begin(); itr
!= m_CreatureEventAI_Event_Map
.end(); ++itr
)
215 for(size_t i
= 0; i
< itr
->second
.size(); ++i
)
217 CreatureEventAI_Event
const& event
= itr
->second
[i
];
219 for(int j
= 0; j
< MAX_ACTIONS
; ++j
)
221 CreatureEventAI_Action
const& action
= event
.action
[j
];
224 case ACTION_T_SUMMON_ID
:
226 if (action
.summon_id
.spawnId
)
227 idx_set
.erase(action
.summon_id
.spawnId
);
237 for(std::set
<int32
>::const_iterator itr
= idx_set
.begin(); itr
!= idx_set
.end(); ++itr
)
238 sLog
.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_summons` but not used in EventAI scripts.",*itr
);
241 // -------------------
242 void CreatureEventAIMgr::LoadCreatureEventAI_Scripts()
244 //Drop Existing EventAI List
245 m_CreatureEventAI_Event_Map
.clear();
248 QueryResult
*result
= WorldDatabase
.Query("SELECT id, creature_id, event_type, event_inverse_phase_mask, event_chance, event_flags, "
249 "event_param1, event_param2, event_param3, event_param4, "
250 "action1_type, action1_param1, action1_param2, action1_param3, "
251 "action2_type, action2_param1, action2_param2, action2_param3, "
252 "action3_type, action3_param1, action3_param2, action3_param3 "
253 "FROM creature_ai_scripts");
256 barGoLink
bar((int)result
->GetRowCount());
262 Field
*fields
= result
->Fetch();
264 CreatureEventAI_Event temp
;
265 temp
.event_id
= EventAI_Type(fields
[0].GetUInt32());
266 uint32 i
= temp
.event_id
;
268 temp
.creature_id
= fields
[1].GetUInt32();
269 uint32 creature_id
= temp
.creature_id
;
271 uint32 e_type
= fields
[2].GetUInt32();
272 //Report any errors in event
273 if (e_type
>= EVENT_T_END
)
275 sLog
.outErrorDb("CreatureEventAI: Event %u have wrong type (%u), skipping.", i
,e_type
);
278 temp
.event_type
= EventAI_Type(e_type
);
280 temp
.event_inverse_phase_mask
= fields
[3].GetUInt32();
281 temp
.event_chance
= fields
[4].GetUInt8();
282 temp
.event_flags
= fields
[5].GetUInt8();
283 temp
.raw
.param1
= fields
[6].GetUInt32();
284 temp
.raw
.param2
= fields
[7].GetUInt32();
285 temp
.raw
.param3
= fields
[8].GetUInt32();
286 temp
.raw
.param4
= fields
[9].GetUInt32();
288 //Creature does not exist in database
289 if (!sCreatureStorage
.LookupEntry
<CreatureInfo
>(temp
.creature_id
))
291 sLog
.outErrorDb("CreatureEventAI: Event %u has script for non-existing creature entry (%u), skipping.", i
, temp
.creature_id
);
295 //No chance of this event occuring
296 if (temp
.event_chance
== 0)
297 sLog
.outErrorDb("CreatureEventAI: Event %u has 0 percent chance. Event will never trigger!", i
);
298 //Chance above 100, force it to be 100
299 else if (temp
.event_chance
> 100)
301 sLog
.outErrorDb("CreatureEventAI: Creature %u are using event %u with more than 100 percent chance. Adjusting to 100 percent.", temp
.creature_id
, i
);
302 temp
.event_chance
= 100;
305 //Individual event checks
306 switch (temp
.event_type
)
309 case EVENT_T_TIMER_OOC
:
310 if (temp
.timer
.initialMax
< temp
.timer
.initialMin
)
311 sLog
.outErrorDb("CreatureEventAI: Creature %u are using timed event(%u) with param2 < param1 (InitialMax < InitialMin). Event will never repeat.", temp
.creature_id
, i
);
312 if (temp
.timer
.repeatMax
< temp
.timer
.repeatMin
)
313 sLog
.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp
.creature_id
, i
);
317 case EVENT_T_TARGET_HP
:
318 case EVENT_T_TARGET_MANA
:
319 if (temp
.percent_range
.percentMax
> 100)
320 sLog
.outErrorDb("CreatureEventAI: Creature %u are using percentage event(%u) with param2 (MinPercent) > 100. Event will never trigger! ", temp
.creature_id
, i
);
322 if (temp
.percent_range
.percentMax
<= temp
.percent_range
.percentMin
)
323 sLog
.outErrorDb("CreatureEventAI: Creature %u are using percentage event(%u) with param1 <= param2 (MaxPercent <= MinPercent). Event will never trigger! ", temp
.creature_id
, i
);
325 if (temp
.event_flags
& EFLAG_REPEATABLE
&& !temp
.percent_range
.repeatMin
&& !temp
.percent_range
.repeatMax
)
327 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
);
328 temp
.event_flags
&= ~EFLAG_REPEATABLE
;
331 case EVENT_T_SPELLHIT
:
332 if (temp
.spell_hit
.spellId
)
334 SpellEntry
const* pSpell
= sSpellStore
.LookupEntry(temp
.spell_hit
.spellId
);
337 sLog
.outErrorDb("CreatureEventAI: Creature %u has non-existant SpellID(%u) defined in event %u.", temp
.creature_id
, temp
.spell_hit
.spellId
, i
);
341 if ((temp
.spell_hit
.schoolMask
& pSpell
->SchoolMask
) != pSpell
->SchoolMask
)
342 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
);
345 if (!temp
.spell_hit
.schoolMask
)
346 sLog
.outErrorDb("CreatureEventAI: Creature %u is using invalid SpellSchoolMask(%u) defined in event %u.", temp
.creature_id
, temp
.spell_hit
.schoolMask
, i
);
348 if (temp
.spell_hit
.repeatMax
< temp
.spell_hit
.repeatMin
)
349 sLog
.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp
.creature_id
, i
);
352 if (temp
.range
.maxDist
< temp
.range
.minDist
)
353 sLog
.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with param2 < param1 (MaxDist < MinDist). Event will never repeat.", temp
.creature_id
, i
);
354 if (temp
.range
.repeatMax
< temp
.range
.repeatMin
)
355 sLog
.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp
.creature_id
, i
);
357 case EVENT_T_OOC_LOS
:
358 if (temp
.ooc_los
.repeatMax
< temp
.ooc_los
.repeatMin
)
359 sLog
.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp
.creature_id
, i
);
361 case EVENT_T_SPAWNED
:
362 switch(temp
.spawned
.condition
)
364 case SPAWNED_EVENT_ALWAY
:
366 case SPAWNED_EVENT_MAP
:
367 if(!sMapStore
.LookupEntry(temp
.spawned
.conditionValue1
))
368 sLog
.outErrorDb("CreatureEventAI: Creature %u are using spawned event(%u) with param1 = %u 'map specific' but map (param2: %u) does not exist. Event will never repeat.", temp
.creature_id
, i
, temp
.spawned
.condition
, temp
.spawned
.conditionValue1
);
370 case SPAWNED_EVENT_ZONE
:
371 if(!GetAreaEntryByAreaID(temp
.spawned
.conditionValue1
))
372 sLog
.outErrorDb("CreatureEventAI: Creature %u are using spawned event(%u) with param1 = %u 'area specific' but area (param2: %u) does not exist. Event will never repeat.", temp
.creature_id
, i
, temp
.spawned
.condition
, temp
.spawned
.conditionValue1
);
374 sLog
.outErrorDb("CreatureEventAI: Creature %u are using invalid spawned event %u mode (%u) in param1", temp
.creature_id
, i
, temp
.spawned
.condition
);
378 case EVENT_T_FRIENDLY_HP
:
379 if (temp
.friendly_hp
.repeatMax
< temp
.friendly_hp
.repeatMin
)
380 sLog
.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp
.creature_id
, i
);
382 case EVENT_T_FRIENDLY_IS_CC
:
383 if (temp
.friendly_is_cc
.repeatMax
< temp
.friendly_is_cc
.repeatMin
)
384 sLog
.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp
.creature_id
, i
);
386 case EVENT_T_FRIENDLY_MISSING_BUFF
:
388 SpellEntry
const* pSpell
= sSpellStore
.LookupEntry(temp
.spell_hit
.spellId
);
391 sLog
.outErrorDb("CreatureEventAI: Creature %u has non-existant SpellID(%u) defined in event %u.", temp
.creature_id
, temp
.spell_hit
.spellId
, i
);
394 if (temp
.friendly_buff
.repeatMax
< temp
.friendly_buff
.repeatMin
)
395 sLog
.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp
.creature_id
, i
);
399 if (temp
.kill
.repeatMax
< temp
.kill
.repeatMin
)
400 sLog
.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp
.creature_id
, i
);
402 case EVENT_T_TARGET_CASTING
:
403 if (temp
.target_casting
.repeatMax
< temp
.target_casting
.repeatMin
)
404 sLog
.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp
.creature_id
, i
);
406 case EVENT_T_SUMMONED_UNIT
:
407 case EVENT_T_SUMMONED_JUST_DIED
:
408 case EVENT_T_SUMMONED_JUST_DESPAWN
:
409 if (!sCreatureStorage
.LookupEntry
<CreatureInfo
>(temp
.summoned
.creatureId
))
410 sLog
.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with not existed creature template id (%u) in param1, skipped.", temp
.creature_id
, i
, temp
.summoned
.creatureId
);
411 if (temp
.summoned
.repeatMax
< temp
.summoned
.repeatMin
)
412 sLog
.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp
.creature_id
, i
);
414 case EVENT_T_QUEST_ACCEPT
:
415 case EVENT_T_QUEST_COMPLETE
:
416 if (!sObjectMgr
.GetQuestTemplate(temp
.quest
.questId
))
417 sLog
.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with not existed qyest id (%u) in param1, skipped.", temp
.creature_id
, i
, temp
.quest
.questId
);
418 sLog
.outErrorDb("CreatureEventAI: Creature %u using not implemented event (%u) in event %u.", temp
.creature_id
, temp
.event_id
, i
);
424 case EVENT_T_REACHED_HOME
:
426 if (temp
.event_flags
& EFLAG_REPEATABLE
)
428 sLog
.outErrorDb("CreatureEventAI: Creature %u has EFLAG_REPEATABLE set. Event can never be repeatable. Removing flag for event %u.", temp
.creature_id
, i
);
429 temp
.event_flags
&= ~EFLAG_REPEATABLE
;
435 case EVENT_T_RECEIVE_EMOTE
:
437 if (!sEmotesTextStore
.LookupEntry(temp
.receive_emote
.emoteId
))
439 sLog
.outErrorDb("CreatureEventAI: Creature %u using event %u: param1 (EmoteTextId: %u) are not valid.",temp
.creature_id
, i
, temp
.receive_emote
.emoteId
);
443 if (!PlayerCondition::IsValid(ConditionType(temp
.receive_emote
.condition
), temp
.receive_emote
.conditionValue1
, temp
.receive_emote
.conditionValue2
))
445 sLog
.outErrorDb("CreatureEventAI: Creature %u using event %u: param2 (Condition: %u) are not valid.",temp
.creature_id
, i
, temp
.receive_emote
.condition
);
449 if (!(temp
.event_flags
& EFLAG_REPEATABLE
))
451 sLog
.outErrorDb("CreatureEventAI: Creature %u using event %u: EFLAG_REPEATABLE not set. Event must always be repeatable. Flag applied.", temp
.creature_id
, i
);
452 temp
.event_flags
|= EFLAG_REPEATABLE
;
459 case EVENT_T_TARGET_BUFFED
:
461 SpellEntry
const* pSpell
= sSpellStore
.LookupEntry(temp
.buffed
.spellId
);
464 sLog
.outErrorDb("CreatureEventAI: Creature %u has non-existant SpellID(%u) defined in event %u.", temp
.creature_id
, temp
.spell_hit
.spellId
, i
);
467 if (temp
.buffed
.repeatMax
< temp
.buffed
.repeatMin
)
468 sLog
.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp
.creature_id
, i
);
473 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
);
477 for (uint32 j
= 0; j
< MAX_ACTIONS
; j
++)
479 uint16 action_type
= fields
[10+(j
*4)].GetUInt16();
480 if (action_type
>= ACTION_T_END
)
482 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u has incorrect action type (%u), replace by ACTION_T_NONE.", i
, j
+1, action_type
);
483 temp
.action
[j
].type
= ACTION_T_NONE
;
487 CreatureEventAI_Action
& action
= temp
.action
[j
];
489 action
.type
= EventAI_ActionType(action_type
);
490 action
.raw
.param1
= fields
[11+(j
*4)].GetUInt32();
491 action
.raw
.param2
= fields
[12+(j
*4)].GetUInt32();
492 action
.raw
.param3
= fields
[13+(j
*4)].GetUInt32();
494 //Report any errors in actions
501 bool not_set
= false;
502 for(int k
= 0; k
< 3; ++k
)
504 if (action
.text
.TextId
[k
])
506 if (k
> 0 && not_set
)
507 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);
509 if(!action
.text
.TextId
[k
])
512 else if(action
.text
.TextId
[k
] > MIN_CREATURE_AI_TEXT_STRING_ID
|| action
.text
.TextId
[k
] <= MAX_CREATURE_AI_TEXT_STRING_ID
)
514 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
]);
515 action
.text
.TextId
[k
] = 0;
517 else if (m_CreatureEventAI_TextMap
.find(action
.text
.TextId
[k
]) == m_CreatureEventAI_TextMap
.end())
519 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u param%d references non-existing entry (%i) in texts table.", i
, j
+1, k
+1, action
.text
.TextId
[k
]);
520 action
.text
.TextId
[k
] = 0;
526 case ACTION_T_SET_FACTION
:
527 if (action
.set_faction
.factionId
!=0 && !sFactionStore
.LookupEntry(action
.set_faction
.factionId
))
529 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent FactionId %u.", i
, j
+1, action
.set_faction
.factionId
);
530 action
.set_faction
.factionId
= 0;
533 case ACTION_T_MORPH_TO_ENTRY_OR_MODEL
:
534 if (action
.morph
.creatureId
!=0 || action
.morph
.modelId
!=0)
536 if (action
.morph
.creatureId
&& !sCreatureStorage
.LookupEntry
<CreatureInfo
>(action
.morph
.creatureId
))
538 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant Creature entry %u.", i
, j
+1, action
.morph
.creatureId
);
539 action
.morph
.creatureId
= 0;
542 if (action
.morph
.modelId
)
544 if (action
.morph
.creatureId
)
546 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u have unused ModelId %u with also set creature id %u.", i
, j
+1, action
.morph
.modelId
,action
.morph
.creatureId
);
547 action
.morph
.modelId
= 0;
549 else if (!sCreatureDisplayInfoStore
.LookupEntry(action
.morph
.modelId
))
551 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant ModelId %u.", i
, j
+1, action
.morph
.modelId
);
552 action
.morph
.modelId
= 0;
558 if (!sSoundEntriesStore
.LookupEntry(action
.sound
.soundId
))
559 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant SoundID %u.", i
, j
+1, action
.sound
.soundId
);
562 if (!sEmotesStore
.LookupEntry(action
.emote
.emoteId
))
563 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u param1 (EmoteId: %u) are not valid.", i
, j
+1, action
.emote
.emoteId
);
565 case ACTION_T_RANDOM_SOUND
:
566 if (!sSoundEntriesStore
.LookupEntry(action
.random_sound
.soundId1
))
567 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u param1 uses non-existant SoundID %u.", i
, j
+1, action
.random_sound
.soundId1
);
568 if (action
.random_sound
.soundId2
>= 0 && !sSoundEntriesStore
.LookupEntry(action
.random_sound
.soundId2
))
569 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u param2 uses non-existant SoundID %u.", i
, j
+1, action
.random_sound
.soundId2
);
570 if (action
.random_sound
.soundId3
>= 0 && !sSoundEntriesStore
.LookupEntry(action
.random_sound
.soundId3
))
571 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u param3 uses non-existant SoundID %u.", i
, j
+1, action
.random_sound
.soundId3
);
573 case ACTION_T_RANDOM_EMOTE
:
574 if (!sEmotesStore
.LookupEntry(action
.random_emote
.emoteId1
))
575 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u param1 (EmoteId: %u) are not valid.", i
, j
+1, action
.random_emote
.emoteId1
);
576 if (action
.random_emote
.emoteId2
>= 0 && !sEmotesStore
.LookupEntry(action
.random_emote
.emoteId2
))
577 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u param2 (EmoteId: %u) are not valid.", i
, j
+1, action
.random_emote
.emoteId2
);
578 if (action
.random_emote
.emoteId3
>= 0 && !sEmotesStore
.LookupEntry(action
.random_emote
.emoteId3
))
579 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u param3 (EmoteId: %u) are not valid.", i
, j
+1, action
.random_emote
.emoteId3
);
583 const SpellEntry
*spell
= sSpellStore
.LookupEntry(action
.cast
.spellId
);
585 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent SpellID %u.", i
, j
+1, action
.cast
.spellId
);
586 /* FIXME: temp.raw.param3 not have event tipes with recovery time in it....
589 if (spell->RecoveryTime > 0 && temp.event_flags & EFLAG_REPEATABLE)
591 //output as debug for now, also because there's no general rule all spells have RecoveryTime
592 if (temp.event_param3 < spell->RecoveryTime)
593 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);
598 //Cast is always triggered if target is forced to cast on self
599 if (action
.cast
.castFlags
& CAST_FORCE_TARGET_SELF
)
600 action
.cast
.castFlags
|= CAST_TRIGGERED
;
602 if (action
.cast
.target
>= TARGET_T_END
)
603 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
606 case ACTION_T_SUMMON
:
607 if (!sCreatureStorage
.LookupEntry
<CreatureInfo
>(action
.summon
.creatureId
))
608 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent creature entry %u.", i
, j
+1, action
.summon
.creatureId
);
610 if (action
.summon
.target
>= TARGET_T_END
)
611 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
613 case ACTION_T_THREAT_SINGLE_PCT
:
614 if (std::abs(action
.threat_single_pct
.percent
) > 100)
615 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses invalid percent value %u.", i
, j
+1, action
.threat_single_pct
.percent
);
616 if (action
.threat_single_pct
.target
>= TARGET_T_END
)
617 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
619 case ACTION_T_THREAT_ALL_PCT
:
620 if (std::abs(action
.threat_all_pct
.percent
) > 100)
621 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses invalid percent value %u.", i
, j
+1, action
.threat_all_pct
.percent
);
623 case ACTION_T_QUEST_EVENT
:
624 if (Quest
const* qid
= sObjectMgr
.GetQuestTemplate(action
.quest_event
.questId
))
626 if (!qid
->HasFlag(QUEST_MANGOS_FLAGS_EXPLORATION_OR_EVENT
))
627 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
);
630 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent Quest entry %u.", i
, j
+1, action
.quest_event
.questId
);
632 if (action
.quest_event
.target
>= TARGET_T_END
)
633 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
636 case ACTION_T_CAST_EVENT
:
637 if (!sCreatureStorage
.LookupEntry
<CreatureInfo
>(action
.cast_event
.creatureId
))
638 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent creature entry %u.", i
, j
+1, action
.cast_event
.creatureId
);
639 if (!sSpellStore
.LookupEntry(action
.cast_event
.spellId
))
640 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent SpellID %u.", i
, j
+1, action
.cast_event
.spellId
);
641 if (action
.cast_event
.target
>= TARGET_T_END
)
642 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
644 case ACTION_T_SET_UNIT_FIELD
:
645 if (action
.set_unit_field
.field
< OBJECT_END
|| action
.set_unit_field
.field
>= UNIT_END
)
646 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u param1 (UNIT_FIELD*). Index out of range for intended use.", i
, j
+1);
647 if (action
.set_unit_field
.target
>= TARGET_T_END
)
648 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
650 case ACTION_T_SET_UNIT_FLAG
:
651 case ACTION_T_REMOVE_UNIT_FLAG
:
652 if (action
.unit_flag
.target
>= TARGET_T_END
)
653 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
655 case ACTION_T_SET_PHASE
:
656 if (action
.set_phase
.phase
>= MAX_PHASE
)
657 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);
659 case ACTION_T_INC_PHASE
:
660 if (action
.set_inc_phase
.step
== 0)
661 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u is incrementing phase by 0. Was this intended?", i
, j
+1);
662 else if (std::abs(action
.set_inc_phase
.step
) > MAX_PHASE
-1)
663 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u is change phase by too large for any use %i.", i
, j
+1, action
.set_inc_phase
.step
);
665 case ACTION_T_QUEST_EVENT_ALL
:
666 if (Quest
const* qid
= sObjectMgr
.GetQuestTemplate(action
.quest_event_all
.questId
))
668 if (!qid
->HasFlag(QUEST_MANGOS_FLAGS_EXPLORATION_OR_EVENT
))
669 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
);
672 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent Quest entry %u.", i
, j
+1, action
.quest_event_all
.questId
);
674 case ACTION_T_CAST_EVENT_ALL
:
675 if (!sCreatureStorage
.LookupEntry
<CreatureInfo
>(action
.cast_event_all
.creatureId
))
676 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent creature entry %u.", i
, j
+1, action
.cast_event_all
.creatureId
);
677 if (!sSpellStore
.LookupEntry(action
.cast_event_all
.spellId
))
678 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent SpellID %u.", i
, j
+1, action
.cast_event_all
.spellId
);
680 case ACTION_T_REMOVEAURASFROMSPELL
:
681 if (!sSpellStore
.LookupEntry(action
.remove_aura
.spellId
))
682 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent SpellID %u.", i
, j
+1, action
.remove_aura
.spellId
);
683 if (action
.remove_aura
.target
>= TARGET_T_END
)
684 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
686 case ACTION_T_RANDOM_PHASE
: //PhaseId1, PhaseId2, PhaseId3
687 if (action
.random_phase
.phase1
>= MAX_PHASE
)
688 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);
689 if (action
.random_phase
.phase2
>= MAX_PHASE
)
690 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);
691 if (action
.random_phase
.phase3
>= MAX_PHASE
)
692 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);
694 case ACTION_T_RANDOM_PHASE_RANGE
: //PhaseMin, PhaseMax
695 if (action
.random_phase_range
.phaseMin
>= MAX_PHASE
)
696 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);
697 if (action
.random_phase_range
.phaseMin
>= MAX_PHASE
)
698 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);
699 if (action
.random_phase_range
.phaseMin
>= action
.random_phase_range
.phaseMax
)
701 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phaseMax <= phaseMin.", i
, j
+1);
702 std::swap(action
.random_phase_range
.phaseMin
,action
.random_phase_range
.phaseMax
);
703 // equal case processed at call
706 case ACTION_T_SUMMON_ID
:
707 if (!sCreatureStorage
.LookupEntry
<CreatureInfo
>(action
.summon_id
.creatureId
))
708 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant creature entry %u.", i
, j
+1, action
.summon_id
.creatureId
);
709 if (action
.summon_id
.target
>= TARGET_T_END
)
710 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
711 if (m_CreatureEventAI_Summon_Map
.find(action
.summon_id
.spawnId
) == m_CreatureEventAI_Summon_Map
.end())
712 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u summons missing CreatureEventAI_Summon %u", i
, j
+1, action
.summon_id
.spawnId
);
714 case ACTION_T_KILLED_MONSTER
:
715 if (!sCreatureStorage
.LookupEntry
<CreatureInfo
>(action
.killed_monster
.creatureId
))
716 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant creature entry %u.", i
, j
+1, action
.killed_monster
.creatureId
);
717 if (action
.killed_monster
.target
>= TARGET_T_END
)
718 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
720 case ACTION_T_SET_INST_DATA
:
721 if (!(temp
.event_flags
& EFLAG_DIFFICULTY_ALL
))
722 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u. Cannot set instance data without difficulty event flags.", i
, j
+1);
723 if (action
.set_inst_data
.value
> 4/*SPECIAL*/)
724 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set instance data above encounter state 4. Custom case?", i
, j
+1);
726 case ACTION_T_SET_INST_DATA64
:
727 if (!(temp
.event_flags
& EFLAG_DIFFICULTY_ALL
))
728 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u. Cannot set instance data without difficulty event flags.", i
, j
+1);
729 if (action
.set_inst_data64
.target
>= TARGET_T_END
)
730 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
732 case ACTION_T_UPDATE_TEMPLATE
:
733 if (!sCreatureStorage
.LookupEntry
<CreatureInfo
>(action
.update_template
.creatureId
))
734 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant creature entry %u.", i
, j
+1, action
.update_template
.creatureId
);
736 case ACTION_T_SET_SHEATH
:
737 if (action
.set_sheath
.sheath
>= MAX_SHEATH_STATE
)
739 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses wrong sheath state %u.", i
, j
+1, action
.set_sheath
.sheath
);
740 action
.set_sheath
.sheath
= SHEATH_STATE_UNARMED
;
743 case ACTION_T_SET_INVINCIBILITY_HP_LEVEL
:
744 if(action
.invincibility_hp_level
.is_percent
)
746 if(action
.invincibility_hp_level
.hp_level
> 100)
748 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses wrong percent value %u.", i
, j
+1, action
.invincibility_hp_level
.hp_level
);
749 action
.invincibility_hp_level
.hp_level
= 100;
753 case ACTION_T_EVADE
: //No Params
754 case ACTION_T_FLEE_FOR_ASSIST
: //No Params
755 case ACTION_T_DIE
: //No Params
756 case ACTION_T_ZONE_COMBAT_PULSE
: //No Params
757 case ACTION_T_FORCE_DESPAWN
: //No Params
758 case ACTION_T_AUTO_ATTACK
: //AllowAttackState (0 = stop attack, anything else means continue attacking)
759 case ACTION_T_COMBAT_MOVEMENT
: //AllowCombatMovement (0 = stop combat based movement, anything else continue attacking)
760 case ACTION_T_RANGED_MOVEMENT
: //Distance, Angle
761 case ACTION_T_CALL_FOR_HELP
: //Distance
764 case ACTION_T_RANDOM_SAY
:
765 case ACTION_T_RANDOM_YELL
:
766 case ACTION_T_RANDOM_TEXTEMOTE
:
767 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u currently unused ACTION type. Did you forget to update database?", i
, j
+1);
770 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u have currently not checked at load action type (%u). Need check code update?", i
, j
+1, temp
.action
[j
].type
);
776 m_CreatureEventAI_Event_Map
[creature_id
].push_back(temp
);
778 } while (result
->NextRow());
782 CheckUnusedAITexts();
783 CheckUnusedAISummons();
786 sLog
.outString(">> Loaded %u CreatureEventAI scripts", Count
);
792 sLog
.outString(">> Loaded 0 CreatureEventAI scripts. DB table `creature_ai_scripts` is empty.");