diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/game/CharacterHandler.cpp | 2 | ||||
-rw-r--r-- | src/game/Creature.cpp | 8 | ||||
-rw-r--r-- | src/game/Creature.h | 2 | ||||
-rw-r--r-- | src/game/GameObject.cpp | 6 | ||||
-rw-r--r-- | src/game/GameObject.h | 2 | ||||
-rw-r--r-- | src/game/Group.cpp | 170 | ||||
-rw-r--r-- | src/game/Group.h | 5 | ||||
-rw-r--r-- | src/game/InstanceSaveMgr.cpp | 4 | ||||
-rw-r--r-- | src/game/ObjectDefines.h | 4 | ||||
-rw-r--r-- | src/game/ObjectMgr.cpp | 205 | ||||
-rw-r--r-- | src/game/ObjectMgr.h | 3 | ||||
-rw-r--r-- | src/game/Player.cpp | 21 |
12 files changed, 212 insertions, 220 deletions
diff --git a/src/game/CharacterHandler.cpp b/src/game/CharacterHandler.cpp index 6fa89113494..666b255147f 100644 --- a/src/game/CharacterHandler.cpp +++ b/src/game/CharacterHandler.cpp @@ -70,7 +70,7 @@ bool LoginQueryHolder::Initialize() "resettalents_time, trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, online, death_expire_time, taxi_path, dungeon_difficulty," "arenaPoints, totalHonorPoints, todayHonorPoints, yesterdayHonorPoints, totalKills, todayKills, yesterdayKills, chosenTitle, knownCurrencies, watchedFaction, drunk," "health, power1, power2, power3, power4, power5, power6, power7, instance_id, speccount, activespec, exploredZones, equipmentCache, ammoId, knownTitles, actionBars FROM characters WHERE guid = '%u'", GUID_LOPART(m_guid)); - res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADGROUP, "SELECT leaderGuid FROM group_member WHERE memberGuid ='%u'", GUID_LOPART(m_guid)); + res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADGROUP, "SELECT guid FROM group_member WHERE memberGuid =%u", GUID_LOPART(m_guid)); res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADBOUNDINSTANCES, "SELECT id, permanent, map, difficulty, resettime FROM character_instance LEFT JOIN instance ON instance = id WHERE guid = '%u'", GUID_LOPART(m_guid)); res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADAURAS, "SELECT caster_guid,spell,effect_mask,recalculate_mask,stackcount,amount0,amount1,amount2,base_amount0,base_amount1,base_amount2,maxduration,remaintime,remaincharges FROM character_aura WHERE guid = '%u'", GUID_LOPART(m_guid)); res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADSPELLS, "SELECT spell,active,disabled FROM character_spell WHERE guid = '%u'", GUID_LOPART(m_guid)); diff --git a/src/game/Creature.cpp b/src/game/Creature.cpp index 34372ac614d..a78829f1669 100644 --- a/src/game/Creature.cpp +++ b/src/game/Creature.cpp @@ -140,7 +140,7 @@ bool ForcedDespawnDelayEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) Creature::Creature() : Unit(), -lootForPickPocketed(false), lootForBody(false), m_groupLootTimer(0), lootingGroupLeaderGUID(0), +lootForPickPocketed(false), lootForBody(false), m_groupLootTimer(0), lootingGroupGUID(0), m_lootMoney(0), m_lootRecipient(0), m_deathTimer(0), m_respawnTime(0), m_respawnDelay(300), m_corpseDelay(60), m_respawnradius(0.0f), m_defaultMovementType(IDLE_MOTION_TYPE), m_DBTableGuid(0), m_equipmentId(0), m_AlreadyCallAssistance(false), @@ -481,18 +481,18 @@ void Creature::Update(uint32 diff) if (m_isDeadByDefault) break; - if (m_groupLootTimer && lootingGroupLeaderGUID) + if (m_groupLootTimer && lootingGroupGUID) { // for delayed spells m_Events.Update(diff); if (m_groupLootTimer <= diff) { - Group* group = objmgr.GetGroupByLeader(lootingGroupLeaderGUID); + Group* group = objmgr.GetGroupByGUID(lootingGroupGUID); if (group) group->EndRoll(&loot); m_groupLootTimer = 0; - lootingGroupLeaderGUID = 0; + lootingGroupGUID = 0; } else m_groupLootTimer -= diff; } diff --git a/src/game/Creature.h b/src/game/Creature.h index fd9499cd75e..d2d93d787da 100644 --- a/src/game/Creature.h +++ b/src/game/Creature.h @@ -597,7 +597,7 @@ class Creature : public Unit, public GridObject<Creature> const CreatureData* GetLinkedRespawnCreatureData() const; uint32 m_groupLootTimer; // (msecs)timer used for group loot - uint64 lootingGroupLeaderGUID; // used to find group which is looting corpse + uint64 lootingGroupGUID; // used to find group which is looting corpse void SendZoneUnderAttackMessage(Player* attacker); diff --git a/src/game/GameObject.cpp b/src/game/GameObject.cpp index dc5038a9eec..1301c727777 100644 --- a/src/game/GameObject.cpp +++ b/src/game/GameObject.cpp @@ -62,7 +62,7 @@ GameObject::GameObject() : WorldObject(), m_goValue(new GameObjectValue) m_rotation = 0; m_groupLootTimer = 0; - lootingGroupLeaderGUID = 0; + lootingGroupGUID = 0; ResetLootMode(); // restore default loot mode } @@ -440,11 +440,11 @@ void GameObject::Update(uint32 diff) { if (m_groupLootTimer <= diff) { - Group* group = objmgr.GetGroupByLeader(lootingGroupLeaderGUID); + Group* group = objmgr.GetGroupByGUID(lootingGroupGUID); if (group) group->EndRoll(&loot); m_groupLootTimer = 0; - lootingGroupLeaderGUID = 0; + lootingGroupGUID = 0; } else m_groupLootTimer -= diff; } diff --git a/src/game/GameObject.h b/src/game/GameObject.h index 783e5d5f768..2154adf80c0 100644 --- a/src/game/GameObject.h +++ b/src/game/GameObject.h @@ -709,7 +709,7 @@ class GameObject : public WorldObject, public GridObject<GameObject> Loot loot; uint32 m_groupLootTimer; // (msecs)timer used for group loot - uint64 lootingGroupLeaderGUID; // used to find group which is looting + uint64 lootingGroupGUID; // used to find group which is looting bool hasQuest(uint32 quest_id) const; bool hasInvolvedQuest(uint32 quest_id) const; diff --git a/src/game/Group.cpp b/src/game/Group.cpp index 9b7ec6ec20d..91d6a263c85 100644 --- a/src/game/Group.cpp +++ b/src/game/Group.cpp @@ -37,14 +37,14 @@ Group::Group() { m_leaderGuid = 0; - m_groupType = (GroupType)0; + m_groupType = GroupType(0); m_bgGroup = NULL; - m_lootMethod = (LootMethod)0; + m_lootMethod = LootMethod(0); m_looterGuid = 0; m_lootThreshold = ITEM_QUALITY_UNCOMMON; m_subGroupsCounts = NULL; - for (int i=0; i<TARGETICONCOUNT; ++i) + for (uint8 i = 0; i < TARGETICONCOUNT; ++i) m_targetIcons[i] = 0; } @@ -80,6 +80,8 @@ Group::~Group() bool Group::Create(const uint64 &guid, const char * name) { + uint32 lowguid = objmgr.GenerateLowGuid(HIGHGUID_GROUP); + m_guid = MAKE_NEW_GUID(lowguid, 0, HIGHGUID_GROUP); m_leaderGuid = guid; m_leaderName = name; @@ -107,80 +109,81 @@ bool Group::Create(const uint64 &guid, const char * name) // store group in database CharacterDatabase.BeginTransaction(); - CharacterDatabase.PExecute("DELETE FROM groups WHERE leaderGuid ='%u'", GUID_LOPART(m_leaderGuid)); - CharacterDatabase.PExecute("DELETE FROM group_member WHERE leaderGuid ='%u'", GUID_LOPART(m_leaderGuid)); - CharacterDatabase.PExecute("INSERT INTO groups (leaderGuid,lootMethod,looterGuid,lootThreshold,icon1,icon2,icon3,icon4,icon5,icon6,icon7,icon8,groupType,difficulty,raiddifficulty) " - "VALUES ('%u','%u','%u','%u','" UI64FMTD "','" UI64FMTD "','" UI64FMTD "','" UI64FMTD "','" UI64FMTD "','" UI64FMTD "','" UI64FMTD "','" UI64FMTD "','%u','%u','%u')", - GUID_LOPART(m_leaderGuid), uint32(m_lootMethod), + CharacterDatabase.PExecute("DELETE FROM groups WHERE guid ='%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM group_member WHERE guid ='%u'", lowguid); + CharacterDatabase.PExecute("INSERT INTO groups (guid,leaderGuid,lootMethod,looterGuid,lootThreshold,icon1,icon2,icon3,icon4,icon5,icon6,icon7,icon8,groupType,difficulty,raiddifficulty) " + "VALUES ('%u','%u','%u','%u','%u','" UI64FMTD "','" UI64FMTD "','" UI64FMTD "','" UI64FMTD "','" UI64FMTD "','" UI64FMTD "','" UI64FMTD "','" UI64FMTD "','%u','%u','%u')", + lowguid, GUID_LOPART(m_leaderGuid), uint32(m_lootMethod), GUID_LOPART(m_looterGuid), uint32(m_lootThreshold), m_targetIcons[0], m_targetIcons[1], m_targetIcons[2], m_targetIcons[3], m_targetIcons[4], m_targetIcons[5], m_targetIcons[6], m_targetIcons[7], uint8(m_groupType), uint32(m_dungeonDifficulty), m_raidDifficulty); + if (!AddMember(guid, name)) + { + CharacterDatabase.RollbackTransaction(); + return false; + } + CharacterDatabase.CommitTransaction(); } - - if (!AddMember(guid, name)) - { - CharacterDatabase.RollbackTransaction(); + else if (!AddMember(guid, name)) return false; - } - - if (!isBGGroup()) CharacterDatabase.CommitTransaction(); return true; } -bool Group::LoadGroupFromDB(const uint64 &leaderGuid, QueryResult_AutoPtr result, bool loadMembers) +bool Group::LoadGroupFromDB(const uint64 &groupGuid, QueryResult_AutoPtr result, bool loadMembers) { if (isBGGroup()) return false; - bool external = true; + uint32 groupLowGuid = GUID_LOPART(groupGuid); if (!result) { - external = false; - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 - result = CharacterDatabase.PQuery("SELECT lootMethod, looterGuid, lootThreshold, icon1, icon2, icon3, icon4, icon5, icon6, icon7, icon8, groupType, difficulty, raiddifficulty FROM groups WHERE leaderGuid ='%u'", GUID_LOPART(leaderGuid)); + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 + result = CharacterDatabase.PQuery("SELECT leaderGuid, lootMethod, looterGuid, lootThreshold, icon1, icon2, icon3, icon4, icon5, icon6, icon7, icon8, groupType, difficulty, raiddifficulty FROM groups WHERE guid=%u", groupLowGuid); + if (!result) return false; } - - m_leaderGuid = leaderGuid; + Field *fields = result->Fetch(); + m_guid = groupGuid; + m_leaderGuid = MAKE_NEW_GUID(fields[0].GetUInt32(), 0, HIGHGUID_PLAYER); // group leader not exist - if (!objmgr.GetPlayerNameByGUID(m_leaderGuid, m_leaderName)) + if (!objmgr.GetPlayerNameByGUID(fields[0].GetUInt32(), m_leaderName)) return false; - m_groupType = GroupType((*result)[11].GetUInt8()); + m_lootMethod = (LootMethod)fields[1].GetUInt8(); + m_looterGuid = MAKE_NEW_GUID(fields[2].GetUInt32(), 0, HIGHGUID_PLAYER); + m_lootThreshold = (ItemQualities)fields[3].GetUInt16(); + + for (uint8 i = 0; i < TARGETICONCOUNT; ++i) + m_targetIcons[i] = fields[4+i].GetUInt64(); + m_groupType = GroupType(fields[12].GetUInt8()); if (m_groupType & GROUPTYPE_RAID) _initRaidSubGroupsCounter(); - uint32 diff = (*result)[12].GetUInt8(); + uint32 diff = fields[13].GetUInt8(); if (diff >= MAX_DUNGEON_DIFFICULTY) diff = DUNGEON_DIFFICULTY_NORMAL; m_dungeonDifficulty = Difficulty(diff); - uint32 r_diff = (*result)[13].GetUInt8(); + uint32 r_diff = fields[14].GetUInt8(); if (r_diff >= MAX_RAID_DIFFICULTY) r_diff = RAID_DIFFICULTY_10MAN_NORMAL; m_raidDifficulty = Difficulty(r_diff); - m_lootMethod = (LootMethod)(*result)[0].GetUInt8(); - m_looterGuid = MAKE_NEW_GUID((*result)[1].GetUInt32(), 0, HIGHGUID_PLAYER); - m_lootThreshold = (ItemQualities)(*result)[2].GetUInt16(); - - for (int i=0; i<TARGETICONCOUNT; ++i) - m_targetIcons[i] = (*result)[3+i].GetUInt64(); - if (loadMembers) { - result = CharacterDatabase.PQuery("SELECT memberGuid, memberFlags, subgroup FROM group_member WHERE leaderGuid ='%u'", GUID_LOPART(leaderGuid)); + result = CharacterDatabase.PQuery("SELECT memberGuid, memberFlags, subgroup FROM group_member WHERE guid=%u", groupLowGuid); if (!result) return false; do { - LoadMemberFromDB((*result)[0].GetUInt32(), (*result)[1].GetUInt8(), (*result)[2].GetUInt8()); + fields = result->Fetch(); + LoadMemberFromDB(fields[0].GetUInt32(), fields[1].GetUInt8(), fields[2].GetUInt8()); } while (result->NextRow()); - // group too small - if (GetMembersCount() < 2) + + if (GetMembersCount() < 2) // group too small return false; } @@ -190,11 +193,14 @@ bool Group::LoadGroupFromDB(const uint64 &leaderGuid, QueryResult_AutoPtr result bool Group::LoadMemberFromDB(uint32 guidLow, uint8 memberFlags, uint8 subgroup) { MemberSlot member; - member.guid = MAKE_NEW_GUID(guidLow, 0, HIGHGUID_PLAYER); + member.guid = MAKE_NEW_GUID(guidLow, 0, HIGHGUID_PLAYER); // skip non-existed member if (!objmgr.GetPlayerNameByGUID(member.guid, member.name)) + { + CharacterDatabase.PQuery("DELETE FROM group_member WHERE memberGuid=%u", guidLow); return false; + } member.group = subgroup; member.flags = memberFlags; @@ -213,7 +219,7 @@ void Group::ConvertToRaid() _initRaidSubGroupsCounter(); if (!isBGGroup()) - CharacterDatabase.PExecute("UPDATE groups SET groupType='%u' WHERE leaderGuid='%u'", uint8(m_groupType), GUID_LOPART(m_leaderGuid)); + CharacterDatabase.PExecute("UPDATE groups SET groupType='%u' WHERE guid='%u'", uint8(m_groupType), GUID_LOPART(m_guid)); SendUpdate(); // update quest related GO states (quest activity dependent from raid membership) @@ -457,14 +463,23 @@ void Group::Disband(bool hideDestroy) if (!isBGGroup()) { + uint32 lowguid = GUID_LOPART(m_guid); CharacterDatabase.BeginTransaction(); - CharacterDatabase.PExecute("DELETE FROM groups WHERE leaderGuid='%u'", GUID_LOPART(m_leaderGuid)); - CharacterDatabase.PExecute("DELETE FROM group_member WHERE leaderGuid='%u'", GUID_LOPART(m_leaderGuid)); + CharacterDatabase.PExecute("DELETE FROM groups WHERE guid=%u", lowguid); + CharacterDatabase.PExecute("DELETE FROM group_member WHERE guid=%u", lowguid); CharacterDatabase.CommitTransaction(); ResetInstances(INSTANCE_RESET_GROUP_DISBAND, false, NULL); ResetInstances(INSTANCE_RESET_GROUP_DISBAND, true, NULL); + // FIXME - Safe check! Debug purposes - Will remove after a time if got no reports + QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT COUNT(1) FROM group_instance WHERE guid=%u", lowguid); + if (result) + { + sLog.outError("Group::Disband: %u instances are not being properly deleted from group %u", (*result)[0].GetUInt8(), lowguid); + CharacterDatabase.PExecute("DELETE FROM group_instance WHERE guid=%u", lowguid); + } } + m_guid = 0; m_leaderGuid = 0; m_leaderName = ""; } @@ -659,12 +674,12 @@ void Group::GroupLoot(Loot *loot, WorldObject* pLootedObject) if (Creature* creature = dynamic_cast<Creature *>(pLootedObject)) { creature->m_groupLootTimer = 60000; - creature->lootingGroupLeaderGUID = GetLeaderGUID(); + creature->lootingGroupGUID = GetGUID(); } else if (GameObject* go = dynamic_cast<GameObject *>(pLootedObject)) { go->m_groupLootTimer = 60000; - go->lootingGroupLeaderGUID = GetLeaderGUID(); + go->lootingGroupGUID = GetGUID(); } } else @@ -748,7 +763,7 @@ void Group::NeedBeforeGreed(Loot *loot, WorldObject* pLootedObject) if (Creature* creature = dynamic_cast<Creature *>(pLootedObject)) { creature->m_groupLootTimer = 60000; - creature->lootingGroupLeaderGUID = GetLeaderGUID(); + creature->lootingGroupGUID = GetGUID(); } } else @@ -1066,8 +1081,8 @@ void Group::SendUpdate() data << uint8(0); data << uint32(0); } - data << uint64(0x50000000FFFFFFFELL); // related to voice chat? - data << uint32(0); // 3.3, value increases every time this packet gets sent + data << uint64(m_guid); + data << uint32(m_counter++); // 3.3, value increases every time this packet gets sent data << uint32(GetMembersCount()-1); for (member_citerator citr2 = m_memberSlots.begin(); citr2 != m_memberSlots.end(); ++citr2) { @@ -1217,14 +1232,14 @@ bool Group::_addMember(const uint64 &guid, const char* name, uint8 group) if (!isRaidGroup()) // reset targetIcons for non-raid-groups { - for (int i=0; i<TARGETICONCOUNT; ++i) + for (uint8 i = 0; i < TARGETICONCOUNT; ++i) m_targetIcons[i] = 0; } if (!isBGGroup()) { // insert into group table - CharacterDatabase.PExecute("INSERT INTO group_member(leaderGuid,memberGuid,memberFlags,subgroup) VALUES('%u','%u','%u','%u')", GUID_LOPART(m_leaderGuid), GUID_LOPART(member.guid), member.flags, member.group); + CharacterDatabase.PExecute("INSERT INTO group_member(guid,memberGuid,memberFlags,subgroup) VALUES(%u,%u,%u,%u)", GUID_LOPART(m_guid), GUID_LOPART(member.guid), member.flags, member.group); } return true; @@ -1259,7 +1274,7 @@ bool Group::_removeMember(const uint64 &guid) } if (!isBGGroup()) - CharacterDatabase.PExecute("DELETE FROM group_member WHERE memberGuid='%u'", GUID_LOPART(guid)); + CharacterDatabase.PExecute("DELETE FROM group_member WHERE memberGuid=%u", GUID_LOPART(guid)); if (m_leaderGuid == guid) // leader was removed { @@ -1288,9 +1303,9 @@ void Group::_setLeader(const uint64 &guid) // in the DB also remove solo binds that will be replaced with permbinds // from the new leader CharacterDatabase.PExecute( - "DELETE FROM group_instance WHERE leaderguid='%u' AND (permanent = 1 OR " + "DELETE FROM group_instance WHERE guid=%u AND (permanent = 1 OR " "instance IN (SELECT instance FROM character_instance WHERE guid = '%u')" - ")", GUID_LOPART(m_leaderGuid), GUID_LOPART(slot->guid) + ")", GUID_LOPART(m_guid), GUID_LOPART(slot->guid) ); Player *player = objmgr.GetPlayer(slot->guid); @@ -1311,17 +1326,13 @@ void Group::_setLeader(const uint64 &guid) } } - // update the group's solo binds to the new leader - CharacterDatabase.PExecute("UPDATE group_instance SET leaderGuid='%u' WHERE leaderGuid = '%u'", GUID_LOPART(slot->guid), GUID_LOPART(m_leaderGuid)); - // copy the permanent binds from the new leader to the group // overwriting the solo binds with permanent ones if necessary // in the DB those have been deleted already Player::ConvertInstancesToGroup(player, this, slot->guid); // update the group leader - CharacterDatabase.PExecute("UPDATE groups SET leaderGuid='%u' WHERE leaderGuid='%u'", GUID_LOPART(slot->guid), GUID_LOPART(m_leaderGuid)); - CharacterDatabase.PExecute("UPDATE group_member SET leaderGuid='%u' WHERE leaderGuid='%u'", GUID_LOPART(slot->guid), GUID_LOPART(m_leaderGuid)); + CharacterDatabase.PExecute("UPDATE groups SET leaderGuid='%u' WHERE guid='%u'", GUID_LOPART(slot->guid), GUID_LOPART(m_guid)); CharacterDatabase.CommitTransaction(); } @@ -1550,7 +1561,7 @@ GroupJoinBattlegroundResult Group::CanJoinBattleGroundQueue(BattleGround const* return ERR_ARENA_TEAM_PARTY_SIZE; if (memberscount > bgEntry->maxGroupSize) // no MinPlayerCount for battlegrounds - return ERR_BATTLEGROUND_NONE; // ERR_GROUP_JOIN_BATTLEGROUND_TOO_MANY handled on client side + return ERR_BATTLEGROUND_NONE; // ERR_GROUP_JOIN_BATTLEGROUND_TOO_MANY handled on client side // get a player as reference, to compare other players' stats to (arena team id, queue id based on level, etc.) Player * reference = GetFirstMember()->getSource(); @@ -1609,7 +1620,7 @@ void Group::SetDungeonDifficulty(Difficulty difficulty) { m_dungeonDifficulty = difficulty; if (!isBGGroup()) - CharacterDatabase.PExecute("UPDATE groups SET difficulty = %u WHERE leaderGuid ='%u'", m_dungeonDifficulty, GUID_LOPART(m_leaderGuid)); + CharacterDatabase.PExecute("UPDATE groups SET difficulty = %u WHERE guid ='%u'", m_dungeonDifficulty, GUID_LOPART(m_guid)); for (GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) { @@ -1625,7 +1636,7 @@ void Group::SetRaidDifficulty(Difficulty difficulty) { m_raidDifficulty = difficulty; if (!isBGGroup()) - CharacterDatabase.PExecute("UPDATE groups SET raiddifficulty = %u WHERE leaderGuid ='%u'", m_raidDifficulty, GUID_LOPART(m_leaderGuid)); + CharacterDatabase.PExecute("UPDATE groups SET raiddifficulty = %u WHERE guid ='%u'", m_raidDifficulty, GUID_LOPART(m_guid)); for (GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) { @@ -1755,31 +1766,25 @@ InstanceGroupBind* Group::GetBoundInstance(Map* aMap) InstanceGroupBind* Group::BindToInstance(InstanceSave *save, bool permanent, bool load) { - if (save && !isBGGroup()) - { - InstanceGroupBind& bind = m_boundInstances[save->GetDifficulty()][save->GetMapId()]; - if (bind.save) - { - // when a boss is killed or when copying the players's binds to the group - if (permanent != bind.perm || save != bind.save) - if (!load) CharacterDatabase.PExecute("UPDATE group_instance SET instance = '%u', permanent = '%u' WHERE leaderGuid = '%u' AND instance = '%u'", save->GetInstanceId(), permanent, GUID_LOPART(GetLeaderGUID()), bind.save->GetInstanceId()); - } - else - if (!load) CharacterDatabase.PExecute("INSERT INTO group_instance (leaderGuid, instance, permanent) VALUES ('%u', '%u', '%u')", GUID_LOPART(GetLeaderGUID()), save->GetInstanceId(), permanent); + if (!save || isBGGroup()) + return NULL; - if (bind.save != save) - { - if (bind.save) bind.save->RemoveGroup(this); - save->AddGroup(this); - } + InstanceGroupBind& bind = m_boundInstances[save->GetDifficulty()][save->GetMapId()]; + if (!load && (!bind.save || permanent != bind.perm || save != bind.save)) + CharacterDatabase.PExecute("REPLACE INTO group_instance (guid, instance, permanent) VALUES (%u, %u, %u)", GUID_LOPART(GetGUID()), save->GetInstanceId(), permanent); - bind.save = save; - bind.perm = permanent; - if (!load) sLog.outDebug("Group::BindToInstance: %d is now bound to map %d, instance %d, difficulty %d", GUID_LOPART(GetLeaderGUID()), save->GetMapId(), save->GetInstanceId(), save->GetDifficulty()); - return &bind; + if (bind.save != save) + { + if (bind.save) + bind.save->RemoveGroup(this); + save->AddGroup(this); } - else - return NULL; + + bind.save = save; + bind.perm = permanent; + if (!load) + sLog.outDebug("Group::BindToInstance: %d is now bound to map %d, instance %d, difficulty %d", GUID_LOPART(GetGUID()), save->GetMapId(), save->GetInstanceId(), save->GetDifficulty()); + return &bind; } void Group::UnbindInstance(uint32 mapid, uint8 difficulty, bool unload) @@ -1787,7 +1792,8 @@ void Group::UnbindInstance(uint32 mapid, uint8 difficulty, bool unload) BoundInstancesMap::iterator itr = m_boundInstances[difficulty].find(mapid); if (itr != m_boundInstances[difficulty].end()) { - if (!unload) CharacterDatabase.PExecute("DELETE FROM group_instance WHERE leaderGuid = '%u' AND instance = '%u'", GUID_LOPART(GetLeaderGUID()), itr->second.save->GetInstanceId()); + if (!unload) + CharacterDatabase.PExecute("DELETE FROM group_instance WHERE guid=%u AND instance=%u", GUID_LOPART(GetGUID()), itr->second.save->GetInstanceId()); itr->second.save->RemoveGroup(this); // save can become invalid m_boundInstances[difficulty].erase(itr); } diff --git a/src/game/Group.h b/src/game/Group.h index 132b5a20b5c..3dc98ee6d04 100644 --- a/src/game/Group.h +++ b/src/game/Group.h @@ -172,7 +172,7 @@ class Group // group manipulation methods bool Create(const uint64 &guid, const char * name); - bool LoadGroupFromDB(const uint64 &leaderGuid, QueryResult_AutoPtr result = QueryResult_AutoPtr(NULL), bool loadMembers = true); + bool LoadGroupFromDB(const uint64 &guid, QueryResult_AutoPtr result = QueryResult_AutoPtr(NULL), bool loadMembers = true); bool LoadMemberFromDB(uint32 guidLow, uint8 memberFlags, uint8 subgroup); bool AddInvite(Player *player); uint32 RemoveInvite(Player *player); @@ -194,6 +194,7 @@ class Group bool isBGGroup() const { return m_bgGroup != NULL; } bool IsCreated() const { return GetMembersCount() > 0; } const uint64& GetLeaderGUID() const { return m_leaderGuid; } + const uint64& GetGUID() const { return m_guid; } const char * GetLeaderName() const { return m_leaderName.c_str(); } LootMethod GetLootMethod() const { return m_lootMethod; } const uint64& GetLooterGuid() const { return m_looterGuid; } @@ -448,5 +449,7 @@ class Group Rolls RollId; BoundInstancesMap m_boundInstances[MAX_DIFFICULTY]; uint8* m_subGroupsCounts; + uint64 m_guid; + uint32 m_counter; // used only in SMSG_GROUP_LIST }; #endif diff --git a/src/game/InstanceSaveMgr.cpp b/src/game/InstanceSaveMgr.cpp index cab7850e3f4..d67364966d6 100644 --- a/src/game/InstanceSaveMgr.cpp +++ b/src/game/InstanceSaveMgr.cpp @@ -266,14 +266,14 @@ void InstanceSaveManager::CleanupInstances() // clean character/group - instance binds with invalid group/characters _DelHelper(CharacterDatabase, "character_instance.guid, instance", "character_instance", "LEFT JOIN characters ON character_instance.guid = characters.guid WHERE characters.guid IS NULL"); - _DelHelper(CharacterDatabase, "group_instance.leaderGuid, instance", "group_instance", "LEFT JOIN characters ON group_instance.leaderGuid = characters.guid LEFT JOIN groups ON group_instance.leaderGuid = groups.leaderGuid WHERE characters.guid IS NULL OR groups.leaderGuid IS NULL"); + _DelHelper(CharacterDatabase, "group_instance.guid, instance", "group_instance", "LEFT JOIN groups ON group_instance.guid = groups.guid LEFT JOIN characters ON groups.leaderGuid = characters.guid WHERE characters.guid IS NULL OR groups.guid IS NULL"); // clean instances that do not have any players or groups bound to them _DelHelper(CharacterDatabase, "id, map, difficulty", "instance", "LEFT JOIN character_instance ON character_instance.instance = id LEFT JOIN group_instance ON group_instance.instance = id WHERE character_instance.instance IS NULL AND group_instance.instance IS NULL"); // clean invalid instance references in other tables _DelHelper(CharacterDatabase, "character_instance.guid, instance", "character_instance", "LEFT JOIN instance ON character_instance.instance = instance.id WHERE instance.id IS NULL"); - _DelHelper(CharacterDatabase, "group_instance.leaderGuid, instance", "group_instance", "LEFT JOIN instance ON group_instance.instance = instance.id WHERE instance.id IS NULL"); + _DelHelper(CharacterDatabase, "guid, instance", "group_instance", "LEFT JOIN instance ON group_instance.instance = instance.id WHERE instance.id IS NULL"); // creature_respawn and gameobject_respawn are in another database // first, obtain total instance set diff --git a/src/game/ObjectDefines.h b/src/game/ObjectDefines.h index 8247b3378c7..a0e5ac1952d 100644 --- a/src/game/ObjectDefines.h +++ b/src/game/ObjectDefines.h @@ -46,6 +46,7 @@ enum HighGuid HIGHGUID_DYNAMICOBJECT = 0xF100, // blizz F100 HIGHGUID_CORPSE = 0xF101, // blizz F100 HIGHGUID_MO_TRANSPORT = 0x1FC0, // blizz 1FC0 (for GAMEOBJECT_TYPE_MO_TRANSPORT) + HIGHGUID_GROUP = 0x1F50, }; #define IS_EMPTY_GUID(Guid) (Guid == 0) @@ -64,6 +65,7 @@ enum HighGuid #define IS_CORPSE_GUID(Guid) (GUID_HIPART(Guid) == HIGHGUID_CORPSE) #define IS_TRANSPORT(Guid) (GUID_HIPART(Guid) == HIGHGUID_TRANSPORT) #define IS_MO_TRANSPORT(Guid) (GUID_HIPART(Guid) == HIGHGUID_MO_TRANSPORT) +#define IS_GROUP(Guid) (GUID_HIPART(Guid) == HIGHGUID_GROUP) // l - OBJECT_FIELD_GUID // e - OBJECT_FIELD_ENTRY for GO (except GAMEOBJECT_TYPE_MO_TRANSPORT) and creatures or UNIT_FIELD_PETNUMBER for pets @@ -86,6 +88,7 @@ inline bool IsGuidHaveEnPart(uint64 const& guid) case HIGHGUID_PLAYER: case HIGHGUID_DYNAMICOBJECT: case HIGHGUID_CORPSE: + case HIGHGUID_GROUP: return false; case HIGHGUID_GAMEOBJECT: case HIGHGUID_TRANSPORT: @@ -115,6 +118,7 @@ inline char const* GetLogNameForGuid(uint64 guid) case HIGHGUID_DYNAMICOBJECT:return "dynobject"; case HIGHGUID_CORPSE: return "corpse"; case HIGHGUID_MO_TRANSPORT: return "mo_transport"; + case HIGHGUID_GROUP: return "group"; default: return "<unknown>"; } diff --git a/src/game/ObjectMgr.cpp b/src/game/ObjectMgr.cpp index c10674f8bd7..8878d636e37 100644 --- a/src/game/ObjectMgr.cpp +++ b/src/game/ObjectMgr.cpp @@ -175,6 +175,7 @@ ObjectMgr::ObjectMgr() m_hiDoGuid = 1; m_hiCorpseGuid = 1; m_hiPetNumber = 1; + m_hiGroupGuid = 1; m_ItemTextId = 1; m_mailid = 1; m_equipmentSetGuid = 1; @@ -219,10 +220,10 @@ ObjectMgr::~ObjectMgr() itr->second.Clear(); } -Group * ObjectMgr::GetGroupByLeader(const uint64 &guid) const +Group * ObjectMgr::GetGroupByGUID(const uint64 &guid) const { for (GroupSet::const_iterator itr = mGroupSet.begin(); itr != mGroupSet.end(); ++itr) - if ((*itr)->GetLeaderGUID() == guid) + if ((*itr)->GetGUID() == guid) return *itr; return NULL; @@ -3414,159 +3415,132 @@ void ObjectMgr::LoadArenaTeams() void ObjectMgr::LoadGroups() { - // -- loading groups -- Group *group = NULL; - uint64 leaderGuid = 0; + Field *fields = NULL; + uint64 groupGuid = 0; uint32 count = 0; - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 - QueryResult_AutoPtr result = CharacterDatabase.Query("SELECT lootMethod, looterGuid, lootThreshold, icon1, icon2, icon3, icon4, icon5, icon6, icon7, icon8, groupType, difficulty, raiddifficulty, leaderGuid FROM groups"); + // Consistency cleaning before load to avoid having to do some checks later + // Delete all members that does not exist + CharacterDatabase.PExecute("DELETE FROM group_member WHERE NOT EXISTS (SELECT guid FROM characters WHERE guid=memberGuid)"); + // Delete all groups whose leader does not exist + CharacterDatabase.PExecute("DELETE FROM groups WHERE NOT EXISTS (SELECT guid FROM characters WHERE guid=leaderGuid)"); + // Delete all groups with less than 2 members + CharacterDatabase.PExecute("DELETE FROM groups WHERE guid NOT IN (SELECT guid FROM group_member GROUP BY guid HAVING COUNT(guid) > 1)"); + // Delete all rows from group_member or group_instance with no group + CharacterDatabase.PExecute("DELETE FROM group_member WHERE guid NOT IN (SELECT guid FROM groups)"); + CharacterDatabase.PExecute("DELETE FROM group_instance WHERE guid NOT IN (SELECT guid FROM groups)"); + + // ----------------------- Load Group definitions + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT leaderGuid, lootMethod, looterGuid, lootThreshold, icon1, icon2, icon3, icon4, icon5, icon6, icon7, icon8, groupType, difficulty, raiddifficulty, guid FROM groups"); if (!result) { barGoLink bar(1); - bar.step(); sLog.outString(); - sLog.outString(">> Loaded %u group definitions", count); + sLog.outString(">> Loaded 0 group definitions"); return; } barGoLink bar(result->GetRowCount()); - do { bar.step(); - Field *fields = result->Fetch(); + fields = result->Fetch(); ++count; - leaderGuid = MAKE_NEW_GUID(fields[14].GetUInt32(),0,HIGHGUID_PLAYER); - group = new Group; - if (!group->LoadGroupFromDB(leaderGuid, result, false)) - { - group->Disband(); - delete group; - continue; - } + groupGuid = MAKE_NEW_GUID(fields[15].GetUInt32(),0,HIGHGUID_GROUP); + group->LoadGroupFromDB(groupGuid, result, false); + // group load will never be false (we have run consistency sql's before loading) AddGroup(group); }while (result->NextRow()); sLog.outString(); sLog.outString(">> Loaded %u group definitions", count); - // -- loading members -- - count = 0; - group = NULL; - leaderGuid = 0; - // 2 3 4 1 1 - result = CharacterDatabase.Query("SELECT memberGuid, memberFlags, subgroup, leaderGuid FROM group_member ORDER BY leaderGuid"); + // ----------------------- Load member + // 0 1 2 3 + result = CharacterDatabase.Query("SELECT guid, memberGuid, memberFlags, subgroup FROM group_member ORDER BY guid"); if (!result) { barGoLink bar2(1); bar2.step(); - } - else - { - barGoLink bar2(result->GetRowCount()); - do - { - bar2.step(); - Field *fields = result->Fetch(); - count++; - leaderGuid = MAKE_NEW_GUID(fields[3].GetUInt32(), 0, HIGHGUID_PLAYER); - if (!group || group->GetLeaderGUID() != leaderGuid) - { - group = GetGroupByLeader(leaderGuid); - if (!group) - { - sLog.outErrorDb("Incorrect entry in group_member table : no group with leader %d for member %d!", fields[3].GetUInt32(), fields[0].GetUInt32()); - CharacterDatabase.PExecute("DELETE FROM group_member WHERE memberGuid = '%d'", fields[0].GetUInt32()); - continue; - } - } - - if (!group->LoadMemberFromDB(fields[0].GetUInt32(), fields[1].GetUInt8(), fields[2].GetUInt8())) - { - sLog.outErrorDb("Incorrect entry in group_member table : member %d cannot be added to player %d's group!", fields[0].GetUInt32(), fields[3].GetUInt32()); - CharacterDatabase.PExecute("DELETE FROM group_member WHERE memberGuid = '%d'", fields[0].GetUInt32()); - } - }while (result->NextRow()); + sLog.outString(); + sLog.outString(">> Loaded 0 group members"); + return; } - // clean groups - // TODO: maybe delete from the DB before loading in this case - for (GroupSet::iterator itr = mGroupSet.begin(); itr != mGroupSet.end();) + barGoLink bar2(result->GetRowCount()); + uint32 groupLowGuid = 0; + count = 0; + do { - if ((*itr)->GetMembersCount() < 2) + bar2.step(); + fields = result->Fetch(); + + if (groupLowGuid != fields[0].GetUInt32()) { - (*itr)->Disband(); - delete *itr; - mGroupSet.erase(itr++); + groupLowGuid = fields[0].GetUInt32(); + groupGuid = MAKE_NEW_GUID(groupLowGuid, 0, HIGHGUID_GROUP); + group = GetGroupByGUID(groupGuid); + // group will never be NULL (we have run consistency sql's before loading) } - else - ++itr; - } + group->LoadMemberFromDB(fields[1].GetUInt32(), fields[2].GetUInt8(), fields[3].GetUInt8()); + ++count; + }while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u group members", count); - // -- loading instances -- - count = 0; - group = NULL; - leaderGuid = 0; - result = CharacterDatabase.Query( - // 0 1 2 3 4 5 - "SELECT leaderGuid, map, instance, permanent, difficulty, resettime, " - // 6 - "(SELECT COUNT(*) FROM character_instance WHERE guid = leaderGuid AND instance = group_instance.instance AND permanent = 1 LIMIT 1) " - "FROM group_instance LEFT JOIN instance ON instance = id ORDER BY leaderGuid" -); + + // ----------------------- Load instance save + // 0 1 2 3 4 5 + result = CharacterDatabase.Query("SELECT guid, map, instance, permanent, difficulty, resettime, " + // 6 + "(SELECT COUNT(1) FROM groups JOIN character_instance ON leaderGuid = groups.guid WHERE instance = group_instance.instance AND permanent = 1 LIMIT 1) " + "FROM group_instance LEFT JOIN instance ON instance = id ORDER BY guid"); if (!result) { barGoLink bar2(1); bar2.step(); + sLog.outString(); + sLog.outString(">> Loaded 0 group-instance saves"); + return; } - else - { - barGoLink bar2(result->GetRowCount()); - do - { - bar2.step(); - Field *fields = result->Fetch(); - count++; - leaderGuid = MAKE_NEW_GUID(fields[0].GetUInt32(), 0, HIGHGUID_PLAYER); - if (!group || group->GetLeaderGUID() != leaderGuid) - { - group = GetGroupByLeader(leaderGuid); - if (!group) - { - sLog.outErrorDb("Incorrect entry in group_instance table : no group with leader %d", fields[0].GetUInt32()); - continue; - } - } - MapEntry const* mapEntry = sMapStore.LookupEntry(fields[1].GetUInt32()); - if (!mapEntry || !mapEntry->IsDungeon()) - { - sLog.outErrorDb("Incorrect entry in group_instance table : no dungeon map %d", fields[1].GetUInt32()); - continue; - } - - uint32 diff = fields[4].GetUInt8(); - if (diff >= (mapEntry->IsRaid() ? MAX_RAID_DIFFICULTY : MAX_DUNGEON_DIFFICULTY)) - { - sLog.outErrorDb("Wrong dungeon difficulty use in group_instance table: %d", diff + 1); - diff = 0; // default for both difficaly types - } + barGoLink bar3(result->GetRowCount()); + count = 0; + do + { + bar3.step(); + fields = result->Fetch(); + groupGuid = MAKE_NEW_GUID(fields[0].GetUInt32(), 0, HIGHGUID_GROUP); + group = GetGroupByGUID(groupGuid); + // group will never be NULL (we have run consistency sql's before loading) - InstanceSave *save = sInstanceSaveManager.AddInstanceSave(mapEntry->MapID, fields[2].GetUInt32(), Difficulty(diff), (time_t)fields[5].GetUInt64(), (fields[6].GetUInt32() == 0), true); - group->BindToInstance(save, fields[3].GetBool(), true); - }while (result->NextRow()); - } + MapEntry const* mapEntry = sMapStore.LookupEntry(fields[1].GetUInt32()); + if (!mapEntry || !mapEntry->IsDungeon()) + { + sLog.outErrorDb("Incorrect entry in group_instance table : no dungeon map %d", fields[1].GetUInt32()); + continue; + } - sLog.outString(); - sLog.outString(">> Loaded %u group-instance binds total", count); + uint32 diff = fields[4].GetUInt8(); + if (diff >= (mapEntry->IsRaid() ? MAX_RAID_DIFFICULTY : MAX_DUNGEON_DIFFICULTY)) + { + sLog.outErrorDb("Wrong dungeon difficulty use in group_instance table: %d", diff + 1); + diff = 0; // default for both difficaly types + } + InstanceSave *save = sInstanceSaveManager.AddInstanceSave(mapEntry->MapID, fields[2].GetUInt32(), Difficulty(diff), time_t(fields[5].GetUInt64()), fields[6].GetBool(), true); + group->BindToInstance(save, fields[3].GetBool(), true); + ++count; + }while (result->NextRow()); sLog.outString(); - sLog.outString(">> Loaded %u group members total", count); + sLog.outString(">> Loaded %u group-instance saves", count); } void ObjectMgr::LoadQuests() @@ -5995,6 +5969,10 @@ void ObjectMgr::SetHighestGuids() result = CharacterDatabase.Query("SELECT MAX(guildid) FROM guild"); if (result) m_guildId = (*result)[0].GetUInt32()+1; + + result = CharacterDatabase.Query("SELECT MAX(guid) FROM groups"); + if (result) + m_hiGroupGuid = (*result)[0].GetUInt32()+1; } uint32 ObjectMgr::GenerateArenaTeamId() @@ -6107,6 +6085,13 @@ uint32 ObjectMgr::GenerateLowGuid(HighGuid guidhigh) World::StopNow(ERROR_EXIT_CODE); } return m_hiDoGuid++; + case HIGHGUID_GROUP: + if (m_hiGroupGuid >= 0xFFFFFFFE) + { + sLog.outError("Group guid overflow!! Can't continue, shutting down server. "); + World::StopNow(ERROR_EXIT_CODE); + } + return m_hiGroupGuid++; default: ASSERT(0); } diff --git a/src/game/ObjectMgr.h b/src/game/ObjectMgr.h index ae0ca3101cc..2bdd4818db1 100644 --- a/src/game/ObjectMgr.h +++ b/src/game/ObjectMgr.h @@ -437,7 +437,7 @@ class ObjectMgr void LoadGameobjectInfo(); void AddGameobjectInfo(GameObjectInfo *goinfo); - Group * GetGroupByLeader(const uint64 &guid) const; + Group * GetGroupByGUID(const uint64 &guid) const; void AddGroup(Group* group) { mGroupSet.insert(group); } void RemoveGroup(Group* group) { mGroupSet.erase(group); } @@ -1003,6 +1003,7 @@ class ObjectMgr uint32 m_hiGoGuid; uint32 m_hiDoGuid; uint32 m_hiCorpseGuid; + uint32 m_hiGroupGuid; QuestMap mQuestTemplates; diff --git a/src/game/Player.cpp b/src/game/Player.cpp index 4a39401c956..f95f42a4e22 100644 --- a/src/game/Player.cpp +++ b/src/game/Player.cpp @@ -4215,15 +4215,12 @@ void Player::DeleteFromDB(uint64 playerguid, uint32 accountId, bool updateRealmC LeaveAllArenaTeams(playerguid); // the player was uninvited already on logout so just remove from group - QueryResult_AutoPtr resultGroup = CharacterDatabase.PQuery("SELECT leaderGuid FROM group_member WHERE memberGuid='%u'", guid); + QueryResult_AutoPtr resultGroup = CharacterDatabase.PQuery("SELECT guid FROM group_member WHERE memberGuid=%u", guid); if (resultGroup) { - uint64 leaderGuid = MAKE_NEW_GUID((*resultGroup)[0].GetUInt32(), 0, HIGHGUID_PLAYER); - Group* group = objmgr.GetGroupByLeader(leaderGuid); - if (group) - { + uint64 guid = MAKE_NEW_GUID((*resultGroup)[0].GetUInt32(), 0, HIGHGUID_GROUP); + if (Group* group = objmgr.GetGroupByGUID(guid)) RemoveFromGroup(group, playerguid); - } } // remove signs from petitions (also remove petitions if owner); @@ -4329,10 +4326,6 @@ void Player::DeleteFromDB(uint64 playerguid, uint32 accountId, bool updateRealmC CharacterDatabase.PExecute("DELETE FROM character_gifts WHERE guid = '%u'",guid); CharacterDatabase.PExecute("DELETE FROM character_homebind WHERE guid = '%u'",guid); CharacterDatabase.PExecute("DELETE FROM character_instance WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM group_instance WHERE leaderGuid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM groups WHERE leaderGuid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM group_member WHERE leaderGuid = '%u' OR memberGuid = '%u'",guid,guid); - CharacterDatabase.PExecute("DELETE FROM groups WHERE leaderGuid = '%u'",guid); CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE guid = '%u'",guid); CharacterDatabase.PExecute("DELETE FROM character_queststatus WHERE guid = '%u'",guid); CharacterDatabase.PExecute("DELETE FROM character_reputation WHERE guid = '%u'",guid); @@ -16962,11 +16955,11 @@ void Player::_LoadSpells(QueryResult_AutoPtr result) void Player::_LoadGroup(QueryResult_AutoPtr result) { - //QueryResult *result = CharacterDatabase.PQuery("SELECT leaderGuid FROM group_member WHERE memberGuid='%u'", GetGUIDLow()); + //QueryResult *result = CharacterDatabase.PQuery("SELECT guid FROM group_member WHERE memberGuid=%u", GetGUIDLow()); if (result) { - uint64 leaderGuid = MAKE_NEW_GUID((*result)[0].GetUInt32(), 0, HIGHGUID_PLAYER); - if (Group* group = objmgr.GetGroupByLeader(leaderGuid)) + uint64 guid = MAKE_NEW_GUID((*result)[0].GetUInt32(), 0, HIGHGUID_GROUP); + if (Group* group = objmgr.GetGroupByGUID(guid)) { uint8 subgroup = group->GetMemberGroup(GetGUID()); SetGroup(group, subgroup); @@ -17029,7 +17022,7 @@ void Player::_LoadBoundInstances(QueryResult_AutoPtr result) if (!perm && group) { - sLog.outError("_LoadBoundInstances: player %s(%d) is in group %d but has a non-permanent character bind to map %d,%d,%d", GetName(), GetGUIDLow(), GUID_LOPART(group->GetLeaderGUID()), mapId, instanceId, difficulty); + sLog.outError("_LoadBoundInstances: player %s(%d) is in group %d but has a non-permanent character bind to map %d,%d,%d", GetName(), GetGUIDLow(), GUID_LOPART(group->GetGUID()), mapId, instanceId, difficulty); CharacterDatabase.PExecute("DELETE FROM character_instance WHERE guid = '%d' AND instance = '%d'", GetGUIDLow(), instanceId); continue; } |