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!");
122 barGoLink
bar( result
->GetRowCount() );
126 Field
*fields
= result
->Fetch();
130 uint16 event_id
= fields
[0].GetUInt16();
133 sLog
.outErrorDb("`game_event` game event id (%i) is reserved and can't be used.",event_id
);
137 GameEventData
& pGameEvent
= mGameEvent
[event_id
];
138 uint64 starttime
= fields
[1].GetUInt64();
139 pGameEvent
.start
= time_t(starttime
);
140 uint64 endtime
= fields
[2].GetUInt64();
141 pGameEvent
.end
= time_t(endtime
);
142 pGameEvent
.occurence
= fields
[3].GetUInt32();
143 pGameEvent
.length
= fields
[4].GetUInt32();
145 if(pGameEvent
.length
==0) // length>0 is validity check
147 sLog
.outErrorDb("`game_event` game event id (%i) have length 0 and can't be used.",event_id
);
151 pGameEvent
.description
= fields
[5].GetCppString();
153 } while( result
->NextRow() );
157 sLog
.outString( ">> Loaded %u game events", count
);
160 mGameEventCreatureGuids
.resize(mGameEvent
.size()*2-1);
162 result
= WorldDatabase
.Query("SELECT creature.guid, game_event_creature.event "
163 "FROM creature JOIN game_event_creature ON creature.guid = game_event_creature.guid");
172 sLog
.outString(">> Loaded %u creatures in game events", count
);
177 barGoLink
bar( result
->GetRowCount() );
180 Field
*fields
= result
->Fetch();
184 uint32 guid
= fields
[0].GetUInt32();
185 int16 event_id
= fields
[1].GetInt16();
187 int32 internal_event_id
= mGameEvent
.size() + event_id
- 1;
189 if(internal_event_id
< 0 || internal_event_id
>= mGameEventCreatureGuids
.size())
191 sLog
.outErrorDb("`game_event_creature` game event id (%i) is out of range compared to max event id in `game_event`",event_id
);
196 GuidList
& crelist
= mGameEventCreatureGuids
[internal_event_id
];
197 crelist
.push_back(guid
);
199 } while( result
->NextRow() );
203 sLog
.outString( ">> Loaded %u creatures in game events", count
);
206 mGameEventGameobjectGuids
.resize(mGameEvent
.size()*2-1);
208 result
= WorldDatabase
.Query("SELECT gameobject.guid, game_event_gameobject.event "
209 "FROM gameobject JOIN game_event_gameobject ON gameobject.guid=game_event_gameobject.guid");
218 sLog
.outString(">> Loaded %u gameobjects in game events", count
);
223 barGoLink
bar( result
->GetRowCount() );
226 Field
*fields
= result
->Fetch();
230 uint32 guid
= fields
[0].GetUInt32();
231 int16 event_id
= fields
[1].GetInt16();
233 int32 internal_event_id
= mGameEvent
.size() + event_id
- 1;
235 if(internal_event_id
< 0 || internal_event_id
>= mGameEventGameobjectGuids
.size())
237 sLog
.outErrorDb("`game_event_gameobject` game event id (%i) is out of range compared to max event id in `game_event`",event_id
);
242 GuidList
& golist
= mGameEventGameobjectGuids
[internal_event_id
];
243 golist
.push_back(guid
);
245 } while( result
->NextRow() );
249 sLog
.outString( ">> Loaded %u gameobjects in game events", count
);
252 mGameEventModelEquip
.resize(mGameEvent
.size());
254 result
= WorldDatabase
.Query("SELECT creature.guid, game_event_model_equip.event, game_event_model_equip.modelid,"
256 "game_event_model_equip.equipment_id "
257 "FROM creature JOIN game_event_model_equip ON creature.guid=game_event_model_equip.guid");
266 sLog
.outString(">> Loaded %u model/equipment changes in game events", count
);
271 barGoLink
bar( result
->GetRowCount() );
274 Field
*fields
= result
->Fetch();
277 uint32 guid
= fields
[0].GetUInt32();
278 uint16 event_id
= fields
[1].GetUInt16();
280 if(event_id
>= mGameEventModelEquip
.size())
282 sLog
.outErrorDb("`game_event_model_equip` game event id (%u) is out of range compared to max event id in `game_event`",event_id
);
287 ModelEquipList
& equiplist
= mGameEventModelEquip
[event_id
];
288 ModelEquip newModelEquipSet
;
289 newModelEquipSet
.modelid
= fields
[2].GetUInt32();
290 newModelEquipSet
.equipment_id
= fields
[3].GetUInt32();
291 newModelEquipSet
.equipement_id_prev
= 0;
292 newModelEquipSet
.modelid_prev
= 0;
294 if(newModelEquipSet
.equipment_id
> 0)
296 if(!objmgr
.GetEquipmentInfo(newModelEquipSet
.equipment_id
))
298 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
);
303 equiplist
.push_back(std::pair
<uint32
, ModelEquip
>(guid
, newModelEquipSet
));
305 } while( result
->NextRow() );
309 sLog
.outString( ">> Loaded %u model/equipment changes in game events", count
);
312 mGameEventQuests
.resize(mGameEvent
.size());
314 result
= WorldDatabase
.Query("SELECT id, quest, event FROM game_event_creature_quest");
323 sLog
.outString(">> Loaded %u quests additions in game events", count
);
328 barGoLink
bar( result
->GetRowCount() );
331 Field
*fields
= result
->Fetch();
334 uint32 id
= fields
[0].GetUInt32();
335 uint32 quest
= fields
[1].GetUInt32();
336 uint16 event_id
= fields
[2].GetUInt16();
338 if(event_id
>= mGameEventQuests
.size())
340 sLog
.outErrorDb("`game_event_creature_quest` game event id (%u) is out of range compared to max event id in `game_event`",event_id
);
345 QuestRelList
& questlist
= mGameEventQuests
[event_id
];
346 questlist
.push_back(QuestRelation(id
, quest
));
348 } while( result
->NextRow() );
352 sLog
.outString( ">> Loaded %u quests additions in game events", count
);
356 uint32
GameEvent::Initialize() // return the next event delay in ms
358 m_ActiveEvents
.clear();
359 uint32 delay
= Update();
360 sLog
.outBasic("Game Event system initialized." );
365 uint32
GameEvent::Update() // return the next event delay in ms
367 uint32 nextEventDelay
= max_ge_check_delay
; // 1 day
369 for (uint16 itr
= 1; itr
< mGameEvent
.size(); ++itr
)
371 //sLog.outErrorDb("Checking event %u",itr);
372 if (CheckOneGameEvent(itr
))
374 //sLog.outDebug("GameEvent %u is active",itr->first);
375 if (!IsActiveEvent(itr
))
380 //sLog.outDebug("GameEvent %u is not active",itr->first);
381 if (IsActiveEvent(itr
))
387 int16 event_nid
= (-1) * (itr
);
388 // spawn all negative ones for this event
389 GameEventSpawn(event_nid
);
393 calcDelay
= NextCheck(itr
);
394 if (calcDelay
< nextEventDelay
)
395 nextEventDelay
= calcDelay
;
397 sLog
.outBasic("Next game event check in %u seconds.", nextEventDelay
+ 1);
398 return (nextEventDelay
+ 1) * 1000; // Add 1 second to be sure event has started/stopped at next call
401 void GameEvent::UnApplyEvent(uint16 event_id
)
403 sLog
.outString("GameEvent %u \"%s\" removed.", event_id
, mGameEvent
[event_id
].description
.c_str());
404 // un-spawn positive event tagged objects
405 GameEventUnspawn(event_id
);
406 // spawn negative event tagget objects
407 int16 event_nid
= (-1) * event_id
;
408 GameEventSpawn(event_nid
);
409 // restore equipment or model
410 ChangeEquipOrModel(event_id
, false);
411 // Remove quests that are events only to non event npc
412 UpdateEventQuests(event_id
, false);
415 void GameEvent::ApplyNewEvent(uint16 event_id
)
417 switch(sWorld
.getConfig(CONFIG_EVENT_ANNOUNCE
))
421 case 1: // announce events
422 sWorld
.SendWorldText(LANG_EVENTMESSAGE
, mGameEvent
[event_id
].description
.c_str());
426 sLog
.outString("GameEvent %u \"%s\" started.", event_id
, mGameEvent
[event_id
].description
.c_str());
427 // spawn positive event tagget objects
428 GameEventSpawn(event_id
);
429 // un-spawn negative event tagged objects
430 int16 event_nid
= (-1) * event_id
;
431 GameEventUnspawn(event_nid
);
432 // Change equipement or model
433 ChangeEquipOrModel(event_id
, true);
434 // Add quests that are events only to non event npc
435 UpdateEventQuests(event_id
, true);
438 void GameEvent::GameEventSpawn(int16 event_id
)
440 int32 internal_event_id
= mGameEvent
.size() + event_id
- 1;
442 if(internal_event_id
< 0 || internal_event_id
>= mGameEventCreatureGuids
.size())
444 sLog
.outError("GameEvent::GameEventSpawn attempt access to out of range mGameEventCreatureGuids element %i (size: %u)",internal_event_id
,mGameEventCreatureGuids
.size());
448 for (GuidList::iterator itr
= mGameEventCreatureGuids
[internal_event_id
].begin();itr
!= mGameEventCreatureGuids
[internal_event_id
].end();++itr
)
450 // Add to correct cell
451 CreatureData
const* data
= objmgr
.GetCreatureData(*itr
);
454 objmgr
.AddCreatureToGrid(*itr
, data
);
456 // Spawn if necessary (loaded grids only)
457 Map
* map
= const_cast<Map
*>(MapManager::Instance().GetBaseMap(data
->mapid
));
458 // We use spawn coords to spawn
459 if(!map
->Instanceable() && !map
->IsRemovalGrid(data
->posX
,data
->posY
))
461 Creature
* pCreature
= new Creature
;
462 //sLog.outDebug("Spawning creature %u",*itr);
463 if (!pCreature
->LoadFromDB(*itr
, map
))
475 if(internal_event_id
< 0 || internal_event_id
>= mGameEventGameobjectGuids
.size())
477 sLog
.outError("GameEvent::GameEventSpawn attempt access to out of range mGameEventGameobjectGuids element %i (size: %u)",internal_event_id
,mGameEventGameobjectGuids
.size());
481 for (GuidList::iterator itr
= mGameEventGameobjectGuids
[internal_event_id
].begin();itr
!= mGameEventGameobjectGuids
[internal_event_id
].end();++itr
)
483 // Add to correct cell
484 GameObjectData
const* data
= objmgr
.GetGOData(*itr
);
487 objmgr
.AddGameobjectToGrid(*itr
, data
);
488 // Spawn if necessary (loaded grids only)
489 // this base map checked as non-instanced and then only existed
490 Map
* map
= const_cast<Map
*>(MapManager::Instance().GetBaseMap(data
->mapid
));
491 // We use current coords to unspawn, not spawn coords since creature can have changed grid
492 if(!map
->Instanceable() && !map
->IsRemovalGrid(data
->posX
, data
->posY
))
494 GameObject
* pGameobject
= new GameObject
;
495 //sLog.outDebug("Spawning gameobject %u", *itr);
496 if (!pGameobject
->LoadFromDB(*itr
, map
))
502 if(pGameobject
->isSpawnedByDefault())
503 map
->Add(pGameobject
);
510 void GameEvent::GameEventUnspawn(int16 event_id
)
512 int32 internal_event_id
= mGameEvent
.size() + event_id
- 1;
514 if(internal_event_id
< 0 || internal_event_id
>= mGameEventCreatureGuids
.size())
516 sLog
.outError("GameEvent::GameEventUnspawn attempt access to out of range mGameEventCreatureGuids element %i (size: %u)",internal_event_id
,mGameEventCreatureGuids
.size());
520 for (GuidList::iterator itr
= mGameEventCreatureGuids
[internal_event_id
].begin();itr
!= mGameEventCreatureGuids
[internal_event_id
].end();++itr
)
522 // Remove the creature from grid
523 if( CreatureData
const* data
= objmgr
.GetCreatureData(*itr
) )
525 objmgr
.RemoveCreatureFromGrid(*itr
, data
);
527 if( Creature
* pCreature
= ObjectAccessor::Instance().GetObjectInWorld(MAKE_NEW_GUID(*itr
, data
->id
, HIGHGUID_UNIT
), (Creature
*)NULL
) )
529 pCreature
->CleanupsBeforeDelete();
530 pCreature
->AddObjectToRemoveList();
535 if(internal_event_id
< 0 || internal_event_id
>= mGameEventGameobjectGuids
.size())
537 sLog
.outError("GameEvent::GameEventUnspawn attempt access to out of range mGameEventGameobjectGuids element %i (size: %u)",internal_event_id
,mGameEventGameobjectGuids
.size());
541 for (GuidList::iterator itr
= mGameEventGameobjectGuids
[internal_event_id
].begin();itr
!= mGameEventGameobjectGuids
[internal_event_id
].end();++itr
)
543 // Remove the gameobject from grid
544 if(GameObjectData
const* data
= objmgr
.GetGOData(*itr
))
546 objmgr
.RemoveGameobjectFromGrid(*itr
, data
);
548 if( GameObject
* pGameobject
= ObjectAccessor::Instance().GetObjectInWorld(MAKE_NEW_GUID(*itr
, data
->id
, HIGHGUID_GAMEOBJECT
), (GameObject
*)NULL
) )
549 pGameobject
->AddObjectToRemoveList();
554 void GameEvent::ChangeEquipOrModel(int16 event_id
, bool activate
)
556 for(ModelEquipList::iterator itr
= mGameEventModelEquip
[event_id
].begin();itr
!= mGameEventModelEquip
[event_id
].end();++itr
)
558 // Remove the creature from grid
559 CreatureData
const* data
= objmgr
.GetCreatureData(itr
->first
);
564 Creature
* pCreature
= ObjectAccessor::Instance().GetObjectInWorld(MAKE_NEW_GUID(itr
->first
, data
->id
,HIGHGUID_UNIT
), (Creature
*)NULL
);
569 itr
->second
.equipement_id_prev
= pCreature
->GetCurrentEquipmentId();
570 itr
->second
.modelid_prev
= pCreature
->GetDisplayId();
571 pCreature
->LoadEquipment(itr
->second
.equipment_id
, true);
572 if (itr
->second
.modelid
>0 && itr
->second
.modelid_prev
!= itr
->second
.modelid
)
574 CreatureModelInfo
const *minfo
= objmgr
.GetCreatureModelInfo(itr
->second
.modelid
);
577 pCreature
->SetDisplayId(itr
->second
.modelid
);
578 pCreature
->SetNativeDisplayId(itr
->second
.modelid
);
579 pCreature
->SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS
,minfo
->bounding_radius
);
580 pCreature
->SetFloatValue(UNIT_FIELD_COMBATREACH
,minfo
->combat_reach
);
586 pCreature
->LoadEquipment(itr
->second
.equipement_id_prev
, true);
587 if (itr
->second
.modelid_prev
>0 && itr
->second
.modelid_prev
!= itr
->second
.modelid
)
589 CreatureModelInfo
const *minfo
= objmgr
.GetCreatureModelInfo(itr
->second
.modelid_prev
);
592 pCreature
->SetDisplayId(itr
->second
.modelid_prev
);
593 pCreature
->SetNativeDisplayId(itr
->second
.modelid_prev
);
594 pCreature
->SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS
,minfo
->bounding_radius
);
595 pCreature
->SetFloatValue(UNIT_FIELD_COMBATREACH
,minfo
->combat_reach
);
600 else // If not spawned
602 CreatureData
const* data
= objmgr
.GetCreatureData(itr
->first
);
603 if (data
&& activate
)
605 CreatureInfo
const *cinfo
= objmgr
.GetCreatureTemplate(data
->id
);
606 uint32 display_id
= objmgr
.ChooseDisplayId(0,cinfo
,data
);
607 CreatureModelInfo
const *minfo
= objmgr
.GetCreatureModelRandomGender(display_id
);
609 display_id
= minfo
->modelid
;
610 if (data
->equipmentId
== 0)
611 itr
->second
.equipement_id_prev
= cinfo
->equipmentId
;
612 else if (data
->equipmentId
!= -1)
613 itr
->second
.equipement_id_prev
= data
->equipmentId
;
614 itr
->second
.modelid_prev
= display_id
;
617 // now last step: put in data
618 // just to have write access to it
619 CreatureData
& data2
= objmgr
.NewOrExistCreatureData(itr
->first
);
622 data2
.displayid
= itr
->second
.modelid
;
623 data2
.equipmentId
= itr
->second
.equipment_id
;
627 data2
.displayid
= itr
->second
.modelid_prev
;
628 data2
.equipmentId
= itr
->second
.equipement_id_prev
;
633 void GameEvent::UpdateEventQuests(uint16 event_id
, bool Activate
)
635 QuestRelList::iterator itr
;
636 for (itr
= mGameEventQuests
[event_id
].begin();itr
!= mGameEventQuests
[event_id
].end();++itr
)
638 QuestRelations
&CreatureQuestMap
= objmgr
.mCreatureQuestRelations
;
639 if (Activate
) // Add the pair(id,quest) to the multimap
640 CreatureQuestMap
.insert(QuestRelations::value_type(itr
->first
, itr
->second
));
642 { // Remove the pair(id,quest) from the multimap
643 QuestRelations::iterator qitr
= CreatureQuestMap
.find(itr
->first
);
644 if (qitr
== CreatureQuestMap
.end())
646 QuestRelations::iterator lastElement
= CreatureQuestMap
.upper_bound(itr
->first
);
647 for ( ;qitr
!= lastElement
;++qitr
)
649 if (qitr
->second
== itr
->second
)
651 CreatureQuestMap
.erase(qitr
); // iterator is now no more valid
652 break; // but we can exit loop since the element is found
659 GameEvent::GameEvent()
661 isSystemInit
= false;