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
19 #include "GameEvent.h"
21 #include "ObjectMgr.h"
22 #include "ProgressBar.h"
25 #include "MapManager.h"
26 #include "Policies/SingletonImp.h"
28 INSTANTIATE_SINGLETON_1(GameEvent
);
30 bool GameEvent::CheckOneGameEvent(uint16 entry
) const
32 // Get the event information
33 time_t currenttime
= time(NULL
);
34 if( mGameEvent
[entry
].start
< currenttime
&& currenttime
< mGameEvent
[entry
].end
&&
35 ((currenttime
- mGameEvent
[entry
].start
) % (mGameEvent
[entry
].occurence
* MINUTE
)) < (mGameEvent
[entry
].length
* MINUTE
) )
41 uint32
GameEvent::NextCheck(uint16 entry
) const
43 time_t currenttime
= time(NULL
);
45 // outdated event: we return max
46 if (currenttime
> mGameEvent
[entry
].end
)
47 return max_ge_check_delay
;
49 // never started event, we return delay before start
50 if (mGameEvent
[entry
].start
> currenttime
)
51 return (mGameEvent
[entry
].start
- currenttime
);
54 // in event, we return the end of it
55 if ((((currenttime
- mGameEvent
[entry
].start
) % (mGameEvent
[entry
].occurence
* 60)) < (mGameEvent
[entry
].length
* 60)))
56 // we return the delay before it ends
57 delay
= (mGameEvent
[entry
].length
* MINUTE
) - ((currenttime
- mGameEvent
[entry
].start
) % (mGameEvent
[entry
].occurence
* MINUTE
));
58 else // not in window, we return the delay before next start
59 delay
= (mGameEvent
[entry
].occurence
* MINUTE
) - ((currenttime
- mGameEvent
[entry
].start
) % (mGameEvent
[entry
].occurence
* MINUTE
));
60 // In case the end is before next check
61 if (mGameEvent
[entry
].end
< time_t(currenttime
+ delay
))
62 return (mGameEvent
[entry
].end
- currenttime
);
67 void GameEvent::StartEvent( uint16 event_id
, bool overwrite
)
69 AddActiveEvent(event_id
);
70 ApplyNewEvent(event_id
);
73 mGameEvent
[event_id
].start
= time(NULL
);
74 if(mGameEvent
[event_id
].end
<= mGameEvent
[event_id
].start
)
75 mGameEvent
[event_id
].end
= mGameEvent
[event_id
].start
+mGameEvent
[event_id
].length
;
79 void GameEvent::StopEvent( uint16 event_id
, bool overwrite
)
81 RemoveActiveEvent(event_id
);
82 UnApplyEvent(event_id
);
85 mGameEvent
[event_id
].start
= time(NULL
) - mGameEvent
[event_id
].length
* MINUTE
;
86 if(mGameEvent
[event_id
].end
<= mGameEvent
[event_id
].start
)
87 mGameEvent
[event_id
].end
= mGameEvent
[event_id
].start
+mGameEvent
[event_id
].length
;
91 void GameEvent::LoadFromDB()
94 QueryResult
*result
= WorldDatabase
.Query("SELECT MAX(entry) FROM game_event");
97 sLog
.outString(">> Table game_event is empty.");
102 Field
*fields
= result
->Fetch();
104 uint32 max_event_id
= fields
[0].GetUInt16();
107 mGameEvent
.resize(max_event_id
+1);
110 QueryResult
*result
= WorldDatabase
.Query("SELECT entry,UNIX_TIMESTAMP(start_time),UNIX_TIMESTAMP(end_time),occurence,length,description FROM game_event");
114 sLog
.outString(">> Table game_event is empty:");
121 barGoLink
bar( result
->GetRowCount() );
125 Field
*fields
= result
->Fetch();
129 uint16 event_id
= fields
[0].GetUInt16();
132 sLog
.outErrorDb("`game_event` game event id (%i) is reserved and can't be used.",event_id
);
136 GameEventData
& pGameEvent
= mGameEvent
[event_id
];
137 uint64 starttime
= fields
[1].GetUInt64();
138 pGameEvent
.start
= time_t(starttime
);
139 uint64 endtime
= fields
[2].GetUInt64();
140 pGameEvent
.end
= time_t(endtime
);
141 pGameEvent
.occurence
= fields
[3].GetUInt32();
142 pGameEvent
.length
= fields
[4].GetUInt32();
144 if(pGameEvent
.length
==0) // length>0 is validity check
146 sLog
.outErrorDb("`game_event` game event id (%i) have length 0 and can't be used.",event_id
);
150 pGameEvent
.description
= fields
[5].GetCppString();
152 } while( result
->NextRow() );
155 sLog
.outString( ">> Loaded %u game events", count
);
158 mGameEventCreatureGuids
.resize(mGameEvent
.size()*2-1);
160 result
= WorldDatabase
.Query("SELECT creature.guid, game_event_creature.event "
161 "FROM creature JOIN game_event_creature ON creature.guid = game_event_creature.guid");
170 sLog
.outString(">> Loaded %u creatures in game events", count
);
175 barGoLink
bar2( result
->GetRowCount() );
178 Field
*fields
= result
->Fetch();
182 uint32 guid
= fields
[0].GetUInt32();
183 int16 event_id
= fields
[1].GetInt16();
185 int32 internal_event_id
= mGameEvent
.size() + event_id
- 1;
187 if(internal_event_id
< 0 || internal_event_id
>= mGameEventCreatureGuids
.size())
189 sLog
.outErrorDb("`game_event_creature` game event id (%i) is out of range compared to max event id in `game_event`",event_id
);
194 GuidList
& crelist
= mGameEventCreatureGuids
[internal_event_id
];
195 crelist
.push_back(guid
);
197 } while( result
->NextRow() );
199 sLog
.outString( ">> Loaded %u creatures in game events", count
);
203 mGameEventGameobjectGuids
.resize(mGameEvent
.size()*2-1);
205 result
= WorldDatabase
.Query("SELECT gameobject.guid, game_event_gameobject.event "
206 "FROM gameobject JOIN game_event_gameobject ON gameobject.guid=game_event_gameobject.guid");
215 sLog
.outString(">> Loaded %u gameobjects in game events", count
);
220 barGoLink
bar3( result
->GetRowCount() );
223 Field
*fields
= result
->Fetch();
227 uint32 guid
= fields
[0].GetUInt32();
228 int16 event_id
= fields
[1].GetInt16();
230 int32 internal_event_id
= mGameEvent
.size() + event_id
- 1;
232 if(internal_event_id
< 0 || internal_event_id
>= mGameEventGameobjectGuids
.size())
234 sLog
.outErrorDb("`game_event_gameobject` game event id (%i) is out of range compared to max event id in `game_event`",event_id
);
239 GuidList
& golist
= mGameEventGameobjectGuids
[internal_event_id
];
240 golist
.push_back(guid
);
242 } while( result
->NextRow() );
244 sLog
.outString( ">> Loaded %u gameobjects in game events", count
);
249 mGameEventModelEquip
.resize(mGameEvent
.size());
251 result
= WorldDatabase
.Query("SELECT creature.guid, game_event_model_equip.event, game_event_model_equip.modelid,"
253 "game_event_model_equip.equipment_id "
254 "FROM creature JOIN game_event_model_equip ON creature.guid=game_event_model_equip.guid");
263 sLog
.outString(">> Loaded %u model/equipment changes in game events", count
);
268 barGoLink
bar3( result
->GetRowCount() );
271 Field
*fields
= result
->Fetch();
274 uint32 guid
= fields
[0].GetUInt32();
275 uint16 event_id
= fields
[1].GetUInt16();
277 if(event_id
>= mGameEventModelEquip
.size())
279 sLog
.outErrorDb("`game_event_model_equip` game event id (%u) is out of range compared to max event id in `game_event`",event_id
);
284 ModelEquipList
& equiplist
= mGameEventModelEquip
[event_id
];
285 ModelEquip newModelEquipSet
;
286 newModelEquipSet
.modelid
= fields
[2].GetUInt32();
287 newModelEquipSet
.equipment_id
= fields
[3].GetUInt32();
288 newModelEquipSet
.equipement_id_prev
= 0;
289 newModelEquipSet
.modelid_prev
= 0;
291 if(newModelEquipSet
.equipment_id
> 0)
293 if(!objmgr
.GetEquipmentInfo(newModelEquipSet
.equipment_id
))
295 sLog
.outErrorDb("Table `game_event_model_equip` have creature (Guid: %u) with equipment_id %u not found in table `creature_equip_template`, set to no equipment.", guid
, newModelEquipSet
.equipment_id
);
300 equiplist
.push_back(std::pair
<uint32
, ModelEquip
>(guid
, newModelEquipSet
));
302 } while( result
->NextRow() );
304 sLog
.outString( ">> Loaded %u model/equipment changes in game events", count
);
309 mGameEventQuests
.resize(mGameEvent
.size());
311 result
= WorldDatabase
.Query("SELECT id, quest, event FROM game_event_creature_quest");
320 sLog
.outString(">> Loaded %u quests additions in game events", count
);
325 barGoLink
bar3( result
->GetRowCount() );
328 Field
*fields
= result
->Fetch();
331 uint32 id
= fields
[0].GetUInt32();
332 uint32 quest
= fields
[1].GetUInt32();
333 uint16 event_id
= fields
[2].GetUInt16();
335 if(event_id
>= mGameEventQuests
.size())
337 sLog
.outErrorDb("`game_event_creature_quest` game event id (%u) is out of range compared to max event id in `game_event`",event_id
);
342 QuestRelList
& questlist
= mGameEventQuests
[event_id
];
343 questlist
.push_back(QuestRelation(id
, quest
));
345 } while( result
->NextRow() );
347 sLog
.outString( ">> Loaded %u quests additions in game events", count
);
353 uint32
GameEvent::Initialize() // return the next event delay in ms
355 m_ActiveEvents
.clear();
356 uint32 delay
= Update();
357 sLog
.outBasic("Game Event system initialized." );
362 uint32
GameEvent::Update() // return the next event delay in ms
364 uint32 nextEventDelay
= max_ge_check_delay
; // 1 day
366 for (uint16 itr
= 1; itr
< mGameEvent
.size(); ++itr
)
368 //sLog.outErrorDb("Checking event %u",itr);
369 if (CheckOneGameEvent(itr
))
371 //sLog.outDebug("GameEvent %u is active",itr->first);
372 if (!IsActiveEvent(itr
))
377 //sLog.outDebug("GameEvent %u is not active",itr->first);
378 if (IsActiveEvent(itr
))
384 int16 event_nid
= (-1) * (itr
);
385 // spawn all negative ones for this event
386 GameEventSpawn(event_nid
);
390 calcDelay
= NextCheck(itr
);
391 if (calcDelay
< nextEventDelay
)
392 nextEventDelay
= calcDelay
;
394 sLog
.outBasic("Next game event check in %u seconds.", nextEventDelay
+ 1);
395 return (nextEventDelay
+ 1) * 1000; // Add 1 second to be sure event has started/stopped at next call
398 void GameEvent::UnApplyEvent(uint16 event_id
)
400 sLog
.outString("GameEvent %u \"%s\" removed.", event_id
, mGameEvent
[event_id
].description
.c_str());
401 // un-spawn positive event tagged objects
402 GameEventUnspawn(event_id
);
403 // spawn negative event tagget objects
404 int16 event_nid
= (-1) * event_id
;
405 GameEventSpawn(event_nid
);
406 // restore equipment or model
407 ChangeEquipOrModel(event_id
, false);
408 // Remove quests that are events only to non event npc
409 UpdateEventQuests(event_id
, false);
412 void GameEvent::ApplyNewEvent(uint16 event_id
)
414 switch(sWorld
.getConfig(CONFIG_EVENT_ANNOUNCE
))
418 case 1: // announce events
419 sWorld
.SendWorldText(LANG_EVENTMESSAGE
, mGameEvent
[event_id
].description
.c_str());
423 sLog
.outString("GameEvent %u \"%s\" started.", event_id
, mGameEvent
[event_id
].description
.c_str());
424 // spawn positive event tagget objects
425 GameEventSpawn(event_id
);
426 // un-spawn negative event tagged objects
427 int16 event_nid
= (-1) * event_id
;
428 GameEventUnspawn(event_nid
);
429 // Change equipement or model
430 ChangeEquipOrModel(event_id
, true);
431 // Add quests that are events only to non event npc
432 UpdateEventQuests(event_id
, true);
435 void GameEvent::GameEventSpawn(int16 event_id
)
437 int32 internal_event_id
= mGameEvent
.size() + event_id
- 1;
439 if(internal_event_id
< 0 || internal_event_id
>= mGameEventCreatureGuids
.size())
441 sLog
.outError("GameEvent::GameEventSpawn attempt access to out of range mGameEventCreatureGuids element %i (size: %u)",internal_event_id
,mGameEventCreatureGuids
.size());
445 for (GuidList::iterator itr
= mGameEventCreatureGuids
[internal_event_id
].begin();itr
!= mGameEventCreatureGuids
[internal_event_id
].end();++itr
)
447 // Add to correct cell
448 CreatureData
const* data
= objmgr
.GetCreatureData(*itr
);
451 objmgr
.AddCreatureToGrid(*itr
, data
);
453 // Spawn if necessary (loaded grids only)
454 Map
* map
= const_cast<Map
*>(MapManager::Instance().GetBaseMap(data
->mapid
));
455 // We use spawn coords to spawn
456 if(!map
->Instanceable() && !map
->IsRemovalGrid(data
->posX
,data
->posY
))
458 Creature
* pCreature
= new Creature
;
459 //sLog.outDebug("Spawning creature %u",*itr);
460 if (!pCreature
->LoadFromDB(*itr
, map
))
472 if(internal_event_id
< 0 || internal_event_id
>= mGameEventGameobjectGuids
.size())
474 sLog
.outError("GameEvent::GameEventSpawn attempt access to out of range mGameEventGameobjectGuids element %i (size: %u)",internal_event_id
,mGameEventGameobjectGuids
.size());
478 for (GuidList::iterator itr
= mGameEventGameobjectGuids
[internal_event_id
].begin();itr
!= mGameEventGameobjectGuids
[internal_event_id
].end();++itr
)
480 // Add to correct cell
481 GameObjectData
const* data
= objmgr
.GetGOData(*itr
);
484 objmgr
.AddGameobjectToGrid(*itr
, data
);
485 // Spawn if necessary (loaded grids only)
486 // this base map checked as non-instanced and then only existed
487 Map
* map
= const_cast<Map
*>(MapManager::Instance().GetBaseMap(data
->mapid
));
488 // We use current coords to unspawn, not spawn coords since creature can have changed grid
489 if(!map
->Instanceable() && !map
->IsRemovalGrid(data
->posX
, data
->posY
))
491 GameObject
* pGameobject
= new GameObject
;
492 //sLog.outDebug("Spawning gameobject %u", *itr);
493 if (!pGameobject
->LoadFromDB(*itr
, map
))
499 if(pGameobject
->isSpawnedByDefault())
500 map
->Add(pGameobject
);
507 void GameEvent::GameEventUnspawn(int16 event_id
)
509 int32 internal_event_id
= mGameEvent
.size() + event_id
- 1;
511 if(internal_event_id
< 0 || internal_event_id
>= mGameEventCreatureGuids
.size())
513 sLog
.outError("GameEvent::GameEventUnspawn attempt access to out of range mGameEventCreatureGuids element %i (size: %u)",internal_event_id
,mGameEventCreatureGuids
.size());
517 for (GuidList::iterator itr
= mGameEventCreatureGuids
[internal_event_id
].begin();itr
!= mGameEventCreatureGuids
[internal_event_id
].end();++itr
)
519 // Remove the creature from grid
520 if( CreatureData
const* data
= objmgr
.GetCreatureData(*itr
) )
522 objmgr
.RemoveCreatureFromGrid(*itr
, data
);
524 if( Creature
* pCreature
= ObjectAccessor::Instance().GetObjectInWorld(MAKE_NEW_GUID(*itr
, data
->id
, HIGHGUID_UNIT
), (Creature
*)NULL
) )
526 pCreature
->CleanupsBeforeDelete();
527 pCreature
->AddObjectToRemoveList();
532 if(internal_event_id
< 0 || internal_event_id
>= mGameEventGameobjectGuids
.size())
534 sLog
.outError("GameEvent::GameEventUnspawn attempt access to out of range mGameEventGameobjectGuids element %i (size: %u)",internal_event_id
,mGameEventGameobjectGuids
.size());
538 for (GuidList::iterator itr
= mGameEventGameobjectGuids
[internal_event_id
].begin();itr
!= mGameEventGameobjectGuids
[internal_event_id
].end();++itr
)
540 // Remove the gameobject from grid
541 if(GameObjectData
const* data
= objmgr
.GetGOData(*itr
))
543 objmgr
.RemoveGameobjectFromGrid(*itr
, data
);
545 if( GameObject
* pGameobject
= ObjectAccessor::Instance().GetObjectInWorld(MAKE_NEW_GUID(*itr
, data
->id
, HIGHGUID_GAMEOBJECT
), (GameObject
*)NULL
) )
546 pGameobject
->AddObjectToRemoveList();
551 void GameEvent::ChangeEquipOrModel(int16 event_id
, bool activate
)
553 for(ModelEquipList::iterator itr
= mGameEventModelEquip
[event_id
].begin();itr
!= mGameEventModelEquip
[event_id
].end();++itr
)
555 // Remove the creature from grid
556 CreatureData
const* data
= objmgr
.GetCreatureData(itr
->first
);
561 Creature
* pCreature
= ObjectAccessor::Instance().GetObjectInWorld(MAKE_NEW_GUID(itr
->first
, data
->id
,HIGHGUID_UNIT
), (Creature
*)NULL
);
566 itr
->second
.equipement_id_prev
= pCreature
->GetCurrentEquipmentId();
567 itr
->second
.modelid_prev
= pCreature
->GetDisplayId();
568 pCreature
->LoadEquipment(itr
->second
.equipment_id
, true);
569 if (itr
->second
.modelid
>0 && itr
->second
.modelid_prev
!= itr
->second
.modelid
)
571 CreatureModelInfo
const *minfo
= objmgr
.GetCreatureModelInfo(itr
->second
.modelid
);
574 pCreature
->SetDisplayId(itr
->second
.modelid
);
575 pCreature
->SetNativeDisplayId(itr
->second
.modelid
);
576 pCreature
->SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS
,minfo
->bounding_radius
);
577 pCreature
->SetFloatValue(UNIT_FIELD_COMBATREACH
,minfo
->combat_reach
);
583 pCreature
->LoadEquipment(itr
->second
.equipement_id_prev
, true);
584 if (itr
->second
.modelid_prev
>0 && itr
->second
.modelid_prev
!= itr
->second
.modelid
)
586 CreatureModelInfo
const *minfo
= objmgr
.GetCreatureModelInfo(itr
->second
.modelid_prev
);
589 pCreature
->SetDisplayId(itr
->second
.modelid_prev
);
590 pCreature
->SetNativeDisplayId(itr
->second
.modelid_prev
);
591 pCreature
->SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS
,minfo
->bounding_radius
);
592 pCreature
->SetFloatValue(UNIT_FIELD_COMBATREACH
,minfo
->combat_reach
);
597 else // If not spawned
599 CreatureData
const* data
= objmgr
.GetCreatureData(itr
->first
);
600 if (data
&& activate
)
602 CreatureInfo
const *cinfo
= objmgr
.GetCreatureTemplate(data
->id
);
603 uint32 display_id
= objmgr
.ChooseDisplayId(0,cinfo
,data
);
604 CreatureModelInfo
const *minfo
= objmgr
.GetCreatureModelRandomGender(display_id
);
606 display_id
= minfo
->modelid
;
607 if (data
->equipmentId
== 0)
608 itr
->second
.equipement_id_prev
= cinfo
->equipmentId
;
609 else if (data
->equipmentId
!= -1)
610 itr
->second
.equipement_id_prev
= data
->equipmentId
;
611 itr
->second
.modelid_prev
= display_id
;
614 // now last step: put in data
615 // just to have write access to it
616 CreatureData
& data2
= objmgr
.NewOrExistCreatureData(itr
->first
);
619 data2
.displayid
= itr
->second
.modelid
;
620 data2
.equipmentId
= itr
->second
.equipment_id
;
624 data2
.displayid
= itr
->second
.modelid_prev
;
625 data2
.equipmentId
= itr
->second
.equipement_id_prev
;
630 void GameEvent::UpdateEventQuests(uint16 event_id
, bool Activate
)
632 QuestRelList::iterator itr
;
633 for (itr
= mGameEventQuests
[event_id
].begin();itr
!= mGameEventQuests
[event_id
].end();++itr
)
635 QuestRelations
&CreatureQuestMap
= objmgr
.mCreatureQuestRelations
;
636 if (Activate
) // Add the pair(id,quest) to the multimap
637 CreatureQuestMap
.insert(QuestRelations::value_type(itr
->first
, itr
->second
));
639 { // Remove the pair(id,quest) from the multimap
640 QuestRelations::iterator qitr
= CreatureQuestMap
.find(itr
->first
);
641 if (qitr
== CreatureQuestMap
.end())
643 QuestRelations::iterator lastElement
= CreatureQuestMap
.upper_bound(itr
->first
);
644 for ( ;qitr
!= lastElement
;++qitr
)
646 if (qitr
->second
== itr
->second
)
648 CreatureQuestMap
.erase(qitr
); // iterator is now no more valid
649 break; // but we can exit loop since the element is found
656 GameEvent::GameEvent()
658 isSystemInit
= false;