2 * Copyright (C) 2005-2009 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 "ObjectDefines.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(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(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(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 with not existed map (%u) in param2. 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 with not existed area (%u) in param2. 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 if (!sCreatureStorage
.LookupEntry
<CreatureInfo
>(temp
.summon_unit
.creatureId
))
408 sLog
.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with not existed creature template id (%u) in param1, skipped.", temp
.creature_id
, i
, temp
.summon_unit
.creatureId
);
409 if (temp
.summon_unit
.repeatMax
< temp
.summon_unit
.repeatMin
)
410 sLog
.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp
.creature_id
, i
);
412 case EVENT_T_QUEST_ACCEPT
:
413 case EVENT_T_QUEST_COMPLETE
:
414 if (!sObjectMgr
.GetQuestTemplate(temp
.quest
.questId
))
415 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
);
416 sLog
.outErrorDb("CreatureEventAI: Creature %u using not implemented event (%u) in event %u.", temp
.creature_id
, temp
.event_id
, i
);
422 case EVENT_T_REACHED_HOME
:
424 if (temp
.event_flags
& EFLAG_REPEATABLE
)
426 sLog
.outErrorDb("CreatureEventAI: Creature %u has EFLAG_REPEATABLE set. Event can never be repeatable. Removing flag for event %u.", temp
.creature_id
, i
);
427 temp
.event_flags
&= ~EFLAG_REPEATABLE
;
433 case EVENT_T_RECEIVE_EMOTE
:
435 if (!sEmotesTextStore
.LookupEntry(temp
.receive_emote
.emoteId
))
437 sLog
.outErrorDb("CreatureEventAI: Creature %u using event %u: param1 (EmoteTextId: %u) are not valid.",temp
.creature_id
, i
, temp
.receive_emote
.emoteId
);
441 if (!PlayerCondition::IsValid(ConditionType(temp
.receive_emote
.condition
), temp
.receive_emote
.conditionValue1
, temp
.receive_emote
.conditionValue2
))
443 sLog
.outErrorDb("CreatureEventAI: Creature %u using event %u: param2 (Condition: %u) are not valid.",temp
.creature_id
, i
, temp
.receive_emote
.condition
);
447 if (!(temp
.event_flags
& EFLAG_REPEATABLE
))
449 sLog
.outErrorDb("CreatureEventAI: Creature %u using event %u: EFLAG_REPEATABLE not set. Event must always be repeatable. Flag applied.", temp
.creature_id
, i
);
450 temp
.event_flags
|= EFLAG_REPEATABLE
;
457 case EVENT_T_TARGET_BUFFED
:
459 SpellEntry
const* pSpell
= sSpellStore
.LookupEntry(temp
.buffed
.spellId
);
462 sLog
.outErrorDb("CreatureEventAI: Creature %u has non-existant SpellID(%u) defined in event %u.", temp
.creature_id
, temp
.spell_hit
.spellId
, i
);
465 if (temp
.buffed
.repeatMax
< temp
.buffed
.repeatMin
)
466 sLog
.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp
.creature_id
, i
);
471 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
);
475 for (uint32 j
= 0; j
< MAX_ACTIONS
; j
++)
477 uint16 action_type
= fields
[10+(j
*4)].GetUInt16();
478 if (action_type
>= ACTION_T_END
)
480 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u has incorrect action type (%u), replace by ACTION_T_NONE.", i
, j
+1, action_type
);
481 temp
.action
[j
].type
= ACTION_T_NONE
;
485 CreatureEventAI_Action
& action
= temp
.action
[j
];
487 action
.type
= EventAI_ActionType(action_type
);
488 action
.raw
.param1
= fields
[11+(j
*4)].GetUInt32();
489 action
.raw
.param2
= fields
[12+(j
*4)].GetUInt32();
490 action
.raw
.param3
= fields
[13+(j
*4)].GetUInt32();
492 //Report any errors in actions
499 bool not_set
= false;
500 for(int k
= 0; k
< 3; ++k
)
502 if (action
.text
.TextId
[k
])
504 if (k
> 0 && not_set
)
505 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);
507 if(!action
.text
.TextId
[k
])
510 else if(action
.text
.TextId
[k
] > MIN_CREATURE_AI_TEXT_STRING_ID
|| action
.text
.TextId
[k
] <= MAX_CREATURE_AI_TEXT_STRING_ID
)
512 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
]);
513 action
.text
.TextId
[k
] = 0;
515 else if (m_CreatureEventAI_TextMap
.find(action
.text
.TextId
[k
]) == m_CreatureEventAI_TextMap
.end())
517 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
]);
518 action
.text
.TextId
[k
] = 0;
524 case ACTION_T_SET_FACTION
:
525 if (action
.set_faction
.factionId
!=0 && !sFactionStore
.LookupEntry(action
.set_faction
.factionId
))
527 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent FactionId %u.", i
, j
+1, action
.set_faction
.factionId
);
528 action
.set_faction
.factionId
= 0;
531 case ACTION_T_MORPH_TO_ENTRY_OR_MODEL
:
532 if (action
.morph
.creatureId
!=0 || action
.morph
.modelId
!=0)
534 if (action
.morph
.creatureId
&& !sCreatureStorage
.LookupEntry
<CreatureInfo
>(action
.morph
.creatureId
))
536 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant Creature entry %u.", i
, j
+1, action
.morph
.creatureId
);
537 action
.morph
.creatureId
= 0;
540 if (action
.morph
.modelId
)
542 if (action
.morph
.creatureId
)
544 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
);
545 action
.morph
.modelId
= 0;
547 else if (!sCreatureDisplayInfoStore
.LookupEntry(action
.morph
.modelId
))
549 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant ModelId %u.", i
, j
+1, action
.morph
.modelId
);
550 action
.morph
.modelId
= 0;
556 if (!sSoundEntriesStore
.LookupEntry(action
.sound
.soundId
))
557 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant SoundID %u.", i
, j
+1, action
.sound
.soundId
);
560 if (!sEmotesStore
.LookupEntry(action
.emote
.emoteId
))
561 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u param1 (EmoteId: %u) are not valid.", i
, j
+1, action
.emote
.emoteId
);
563 case ACTION_T_RANDOM_SOUND
:
564 if (!sSoundEntriesStore
.LookupEntry(action
.random_sound
.soundId1
))
565 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u param1 uses non-existant SoundID %u.", i
, j
+1, action
.random_sound
.soundId1
);
566 if (action
.random_sound
.soundId2
>= 0 && !sSoundEntriesStore
.LookupEntry(action
.random_sound
.soundId2
))
567 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u param2 uses non-existant SoundID %u.", i
, j
+1, action
.random_sound
.soundId2
);
568 if (action
.random_sound
.soundId3
>= 0 && !sSoundEntriesStore
.LookupEntry(action
.random_sound
.soundId3
))
569 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u param3 uses non-existant SoundID %u.", i
, j
+1, action
.random_sound
.soundId3
);
571 case ACTION_T_RANDOM_EMOTE
:
572 if (!sEmotesStore
.LookupEntry(action
.random_emote
.emoteId1
))
573 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u param1 (EmoteId: %u) are not valid.", i
, j
+1, action
.random_emote
.emoteId1
);
574 if (action
.random_emote
.emoteId2
>= 0 && !sEmotesStore
.LookupEntry(action
.random_emote
.emoteId2
))
575 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u param2 (EmoteId: %u) are not valid.", i
, j
+1, action
.random_emote
.emoteId2
);
576 if (action
.random_emote
.emoteId3
>= 0 && !sEmotesStore
.LookupEntry(action
.random_emote
.emoteId3
))
577 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u param3 (EmoteId: %u) are not valid.", i
, j
+1, action
.random_emote
.emoteId3
);
581 const SpellEntry
*spell
= sSpellStore
.LookupEntry(action
.cast
.spellId
);
583 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent SpellID %u.", i
, j
+1, action
.cast
.spellId
);
584 /* FIXME: temp.raw.param3 not have event tipes with recovery time in it....
587 if (spell->RecoveryTime > 0 && temp.event_flags & EFLAG_REPEATABLE)
589 //output as debug for now, also because there's no general rule all spells have RecoveryTime
590 if (temp.event_param3 < spell->RecoveryTime)
591 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);
596 //Cast is always triggered if target is forced to cast on self
597 if (action
.cast
.castFlags
& CAST_FORCE_TARGET_SELF
)
598 action
.cast
.castFlags
|= CAST_TRIGGERED
;
600 if (action
.cast
.target
>= TARGET_T_END
)
601 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
604 case ACTION_T_SUMMON
:
605 if (!sCreatureStorage
.LookupEntry
<CreatureInfo
>(action
.summon
.creatureId
))
606 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent creature entry %u.", i
, j
+1, action
.summon
.creatureId
);
608 if (action
.summon
.target
>= TARGET_T_END
)
609 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
611 case ACTION_T_THREAT_SINGLE_PCT
:
612 if (std::abs(action
.threat_single_pct
.percent
) > 100)
613 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses invalid percent value %u.", i
, j
+1, action
.threat_single_pct
.percent
);
614 if (action
.threat_single_pct
.target
>= TARGET_T_END
)
615 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
617 case ACTION_T_THREAT_ALL_PCT
:
618 if (std::abs(action
.threat_all_pct
.percent
) > 100)
619 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses invalid percent value %u.", i
, j
+1, action
.threat_all_pct
.percent
);
621 case ACTION_T_QUEST_EVENT
:
622 if (Quest
const* qid
= sObjectMgr
.GetQuestTemplate(action
.quest_event
.questId
))
624 if (!qid
->HasFlag(QUEST_MANGOS_FLAGS_EXPLORATION_OR_EVENT
))
625 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
);
628 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent Quest entry %u.", i
, j
+1, action
.quest_event
.questId
);
630 if (action
.quest_event
.target
>= TARGET_T_END
)
631 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
634 case ACTION_T_CAST_EVENT
:
635 if (!sCreatureStorage
.LookupEntry
<CreatureInfo
>(action
.cast_event
.creatureId
))
636 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent creature entry %u.", i
, j
+1, action
.cast_event
.creatureId
);
637 if (!sSpellStore
.LookupEntry(action
.cast_event
.spellId
))
638 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent SpellID %u.", i
, j
+1, action
.cast_event
.spellId
);
639 if (action
.cast_event
.target
>= TARGET_T_END
)
640 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
642 case ACTION_T_SET_UNIT_FIELD
:
643 if (action
.set_unit_field
.field
< OBJECT_END
|| action
.set_unit_field
.field
>= UNIT_END
)
644 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u param1 (UNIT_FIELD*). Index out of range for intended use.", i
, j
+1);
645 if (action
.set_unit_field
.target
>= TARGET_T_END
)
646 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
648 case ACTION_T_SET_UNIT_FLAG
:
649 case ACTION_T_REMOVE_UNIT_FLAG
:
650 if (action
.unit_flag
.target
>= TARGET_T_END
)
651 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
653 case ACTION_T_SET_PHASE
:
654 if (action
.set_phase
.phase
>= MAX_PHASE
)
655 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);
657 case ACTION_T_INC_PHASE
:
658 if (action
.set_inc_phase
.step
== 0)
659 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u is incrementing phase by 0. Was this intended?", i
, j
+1);
660 else if (std::abs(action
.set_inc_phase
.step
) > MAX_PHASE
-1)
661 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
);
663 case ACTION_T_QUEST_EVENT_ALL
:
664 if (Quest
const* qid
= sObjectMgr
.GetQuestTemplate(action
.quest_event_all
.questId
))
666 if (!qid
->HasFlag(QUEST_MANGOS_FLAGS_EXPLORATION_OR_EVENT
))
667 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
);
670 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent Quest entry %u.", i
, j
+1, action
.quest_event_all
.questId
);
672 case ACTION_T_CAST_EVENT_ALL
:
673 if (!sCreatureStorage
.LookupEntry
<CreatureInfo
>(action
.cast_event_all
.creatureId
))
674 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent creature entry %u.", i
, j
+1, action
.cast_event_all
.creatureId
);
675 if (!sSpellStore
.LookupEntry(action
.cast_event_all
.spellId
))
676 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent SpellID %u.", i
, j
+1, action
.cast_event_all
.spellId
);
678 case ACTION_T_REMOVEAURASFROMSPELL
:
679 if (!sSpellStore
.LookupEntry(action
.remove_aura
.spellId
))
680 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent SpellID %u.", i
, j
+1, action
.remove_aura
.spellId
);
681 if (action
.remove_aura
.target
>= TARGET_T_END
)
682 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
684 case ACTION_T_RANDOM_PHASE
: //PhaseId1, PhaseId2, PhaseId3
685 if (action
.random_phase
.phase1
>= MAX_PHASE
)
686 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);
687 if (action
.random_phase
.phase2
>= MAX_PHASE
)
688 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);
689 if (action
.random_phase
.phase3
>= MAX_PHASE
)
690 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);
692 case ACTION_T_RANDOM_PHASE_RANGE
: //PhaseMin, PhaseMax
693 if (action
.random_phase_range
.phaseMin
>= MAX_PHASE
)
694 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);
695 if (action
.random_phase_range
.phaseMin
>= MAX_PHASE
)
696 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);
697 if (action
.random_phase_range
.phaseMin
>= action
.random_phase_range
.phaseMax
)
699 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phaseMax <= phaseMin.", i
, j
+1);
700 std::swap(action
.random_phase_range
.phaseMin
,action
.random_phase_range
.phaseMax
);
701 // equal case processed at call
704 case ACTION_T_SUMMON_ID
:
705 if (!sCreatureStorage
.LookupEntry
<CreatureInfo
>(action
.summon_id
.creatureId
))
706 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant creature entry %u.", i
, j
+1, action
.summon_id
.creatureId
);
707 if (action
.summon_id
.target
>= TARGET_T_END
)
708 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
709 if (m_CreatureEventAI_Summon_Map
.find(action
.summon_id
.spawnId
) == m_CreatureEventAI_Summon_Map
.end())
710 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u summons missing CreatureEventAI_Summon %u", i
, j
+1, action
.summon_id
.spawnId
);
712 case ACTION_T_KILLED_MONSTER
:
713 if (!sCreatureStorage
.LookupEntry
<CreatureInfo
>(action
.killed_monster
.creatureId
))
714 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant creature entry %u.", i
, j
+1, action
.killed_monster
.creatureId
);
715 if (action
.killed_monster
.target
>= TARGET_T_END
)
716 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
718 case ACTION_T_SET_INST_DATA
:
719 if (!(temp
.event_flags
& EFLAG_DIFFICULTY_ALL
))
720 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u. Cannot set instance data without difficulty event flags.", i
, j
+1);
721 if (action
.set_inst_data
.value
> 4/*SPECIAL*/)
722 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set instance data above encounter state 4. Custom case?", i
, j
+1);
724 case ACTION_T_SET_INST_DATA64
:
725 if (!(temp
.event_flags
& EFLAG_DIFFICULTY_ALL
))
726 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u. Cannot set instance data without difficulty event flags.", i
, j
+1);
727 if (action
.set_inst_data64
.target
>= TARGET_T_END
)
728 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i
, j
+1);
730 case ACTION_T_UPDATE_TEMPLATE
:
731 if (!sCreatureStorage
.LookupEntry
<CreatureInfo
>(action
.update_template
.creatureId
))
732 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant creature entry %u.", i
, j
+1, action
.update_template
.creatureId
);
734 case ACTION_T_SET_SHEATH
:
735 if (action
.set_sheath
.sheath
>= MAX_SHEATH_STATE
)
737 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses wrong sheath state %u.", i
, j
+1, action
.set_sheath
.sheath
);
738 action
.set_sheath
.sheath
= SHEATH_STATE_UNARMED
;
741 case ACTION_T_SET_INVINCIBILITY_HP_LEVEL
:
742 if(action
.invincibility_hp_level
.is_percent
)
744 if(action
.invincibility_hp_level
.hp_level
> 100)
746 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u uses wrong percent value %u.", i
, j
+1, action
.invincibility_hp_level
.hp_level
);
747 action
.invincibility_hp_level
.hp_level
= 100;
751 case ACTION_T_EVADE
: //No Params
752 case ACTION_T_FLEE_FOR_ASSIST
: //No Params
753 case ACTION_T_DIE
: //No Params
754 case ACTION_T_ZONE_COMBAT_PULSE
: //No Params
755 case ACTION_T_FORCE_DESPAWN
: //No Params
756 case ACTION_T_AUTO_ATTACK
: //AllowAttackState (0 = stop attack, anything else means continue attacking)
757 case ACTION_T_COMBAT_MOVEMENT
: //AllowCombatMovement (0 = stop combat based movement, anything else continue attacking)
758 case ACTION_T_RANGED_MOVEMENT
: //Distance, Angle
759 case ACTION_T_CALL_FOR_HELP
: //Distance
762 case ACTION_T_RANDOM_SAY
:
763 case ACTION_T_RANDOM_YELL
:
764 case ACTION_T_RANDOM_TEXTEMOTE
:
765 sLog
.outErrorDb("CreatureEventAI: Event %u Action %u currently unused ACTION type. Did you forget to update database?", i
, j
+1);
768 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
);
774 m_CreatureEventAI_Event_Map
[creature_id
].push_back(temp
);
776 } while (result
->NextRow());
780 CheckUnusedAITexts();
781 CheckUnusedAISummons();
784 sLog
.outString(">> Loaded %u CreatureEventAI scripts", Count
);
790 sLog
.outString(">> Loaded 0 CreatureEventAI scripts. DB table `creature_ai_scripts` is empty.");