diff options
Diffstat (limited to 'src')
22 files changed, 386 insertions, 103 deletions
diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 5e419c2017f..335df6505a6 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -1401,7 +1401,7 @@ void Object::AddDynamicValue(uint16 index, uint32 value) if (mask.GetCount() < values.size()) mask.AddBlock(); - mask.SetBit(values.size()); + mask.SetBit(values.size() - 1); if (m_inWorld && !m_objectUpdated) { diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 0a95fed3138..46e455765d3 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -2935,6 +2935,7 @@ void Player::GiveLevel(uint8 level) SetLevel(level); UpdateSkillsForLevel(); + LearnSpecializationSpells(); // save base values (bonuses already included in stored stats for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i) @@ -3046,6 +3047,7 @@ void Player::InitStatsForLevel(bool reapplyMods) SetUInt32Value(UNIT_FIELD_AURASTATE, 0); UpdateSkillsForLevel(); + LearnSpecializationSpells(); // set default cast time multiplier SetFloatValue(UNIT_MOD_CAST_SPEED, 1.0f); @@ -4141,12 +4143,6 @@ bool Player::ResetTalents(bool noCost) RemoveTalent(talentInfo); } - // Remove spec specific spells - RemoveSpecializationSpells(); - - SetSpecId(GetActiveTalentGroup(), 0); - SetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID, 0); - SQLTransaction trans = CharacterDatabase.BeginTransaction(); _SaveTalents(trans); _SaveSpells(trans); @@ -27163,25 +27159,28 @@ Difficulty Player::CheckLoadedLegacyRaidDifficultyID(Difficulty difficulty) SpellInfo const* Player::GetCastSpellInfo(SpellInfo const* spellInfo) const { - auto range = m_overrideSpells.equal_range(spellInfo->Id); - for (auto itr = range.first; itr != range.second; ++itr) - if (SpellInfo const* newInfo = sSpellMgr->GetSpellInfo(itr->second)) + auto overrides = m_overrideSpells.find(spellInfo->Id); + for (uint32 spellId : overrides->second) + if (SpellInfo const* newInfo = sSpellMgr->GetSpellInfo(spellId)) return Unit::GetCastSpellInfo(newInfo); return Unit::GetCastSpellInfo(spellInfo); } +void Player::AddOverrideSpell(uint32 overridenSpellId, uint32 newSpellId) +{ + m_overrideSpells[overridenSpellId].insert(newSpellId); +} + void Player::RemoveOverrideSpell(uint32 overridenSpellId, uint32 newSpellId) { - auto range = m_overrideSpells.equal_range(overridenSpellId); - for (auto itr = range.first; itr != range.second; ++itr) - { - if (itr->second == newSpellId) - { - m_overrideSpells.erase(itr); - break; - } - } + auto overrides = m_overrideSpells.find(overridenSpellId); + if (overrides == m_overrideSpells.end()) + return; + + overrides->second.erase(newSpellId); + if (overrides->second.empty()) + m_overrideSpells.erase(overrides); } void Player::LearnSpecializationSpells() @@ -27191,6 +27190,10 @@ void Player::LearnSpecializationSpells() for (size_t j = 0; j < specSpells->size(); ++j) { SpecializationSpellsEntry const* specSpell = specSpells->at(j); + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(specSpell->SpellID); + if (!spellInfo || spellInfo->SpellLevel > getLevel()) + continue; + LearnSpell(specSpell->SpellID, false); if (specSpell->OverridesSpellID) AddOverrideSpell(specSpell->OverridesSpellID, specSpell->SpellID); diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 064fba79fb1..7366bde52d7 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1837,7 +1837,7 @@ class Player : public Unit, public GridObject<Player> void LearnSpellHighestRank(uint32 spellid); void AddTemporarySpell(uint32 spellId); void RemoveTemporarySpell(uint32 spellId); - void AddOverrideSpell(uint32 overridenSpellId, uint32 newSpellId) { m_overrideSpells.emplace(overridenSpellId, newSpellId); } + void AddOverrideSpell(uint32 overridenSpellId, uint32 newSpellId); void RemoveOverrideSpell(uint32 overridenSpellId, uint32 newSpellId); void LearnSpecializationSpells(); void RemoveSpecializationSpells(); @@ -2795,7 +2795,7 @@ class Player : public Unit, public GridObject<Player> PlayerMails m_mail; PlayerSpellMap m_spells; - std::unordered_multimap<uint32 /*overridenSpellId*/, uint32 /*newSpellId*/> m_overrideSpells; + std::unordered_map<uint32 /*overridenSpellId*/, std::unordered_set<uint32> /*newSpellId*/> m_overrideSpells; uint32 m_lastPotionId; // last used health/mana potion in combat, that block next potion use GlobalCooldownMgr m_GlobalCooldownMgr; diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index 16fb72c88a4..f121eefa71c 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -867,24 +867,7 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder* holder) SendPacket(accountDataTimes.Write()); - /// Send FeatureSystemStatus - { - WorldPackets::System::FeatureSystemStatus features; - - /// START OF DUMMY VALUES - features.ComplaintStatus = 2; - features.ScrollOfResurrectionRequestsRemaining = 1; - features.ScrollOfResurrectionMaxRequestsPerDay = 1; - features.CfgRealmID = 2; - features.CfgRealmRecID = 0; - features.VoiceEnabled = false; - /// END OF DUMMY VALUES - - features.CharUndeleteEnabled = sWorld->getBoolConfig(CONFIG_FEATURE_SYSTEM_CHARACTER_UNDELETE_ENABLED); - features.BpayStoreEnabled = sWorld->getBoolConfig(CONFIG_FEATURE_SYSTEM_BPAY_STORE_ENABLED); - - SendPacket(features.Write()); - } + SendFeatureSystemStatus(); // Send MOTD { @@ -1091,10 +1074,36 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder* holder) delete holder; } -void WorldSession::HandleSetFactionAtWar(WorldPacket& recvData) +void WorldSession::SendFeatureSystemStatus() { - TC_LOG_DEBUG("network", "WORLD: Received CMSG_SET_FACTION_AT_WAR"); + WorldPackets::System::FeatureSystemStatus features; + + /// START OF DUMMY VALUES + features.ComplaintStatus = 2; + features.ScrollOfResurrectionRequestsRemaining = 1; + features.ScrollOfResurrectionMaxRequestsPerDay = 1; + features.CfgRealmID = 2; + features.CfgRealmRecID = 0; + features.VoiceEnabled = false; + features.BrowserEnabled = false; // Has to be false, otherwise client will crash if "Customer Support" is opened + + features.EuropaTicketSystemStatus.HasValue = true; + features.EuropaTicketSystemStatus.Value.ThrottleState.MaxTries = 5; + features.EuropaTicketSystemStatus.Value.ThrottleState.PerMilliseconds = 5; + features.EuropaTicketSystemStatus.Value.ThrottleState.TryCount = 0; + features.EuropaTicketSystemStatus.Value.ThrottleState.LastResetTimeBeforeNow = time(nullptr) - 5000; + /// END OF DUMMY VALUES + + features.EuropaTicketSystemStatus.Value.SubmitBugEnabled = sWorld->getBoolConfig(CONFIG_TICKET_SUBMIT_BUG); + features.EuropaTicketSystemStatus.Value.TicketSystemEnabled = sWorld->getBoolConfig(CONFIG_TICKET_SUBMIT_TICKET); + features.CharUndeleteEnabled = sWorld->getBoolConfig(CONFIG_FEATURE_SYSTEM_CHARACTER_UNDELETE_ENABLED); + features.BpayStoreEnabled = sWorld->getBoolConfig(CONFIG_FEATURE_SYSTEM_BPAY_STORE_ENABLED); + + SendPacket(features.Write()); +} +void WorldSession::HandleSetFactionAtWar(WorldPacket& recvData) +{ uint32 repListID; uint8 flag; diff --git a/src/server/game/Handlers/TicketHandler.cpp b/src/server/game/Handlers/TicketHandler.cpp index a6b58f18ef4..5b2e197ae7a 100644 --- a/src/server/game/Handlers/TicketHandler.cpp +++ b/src/server/game/Handlers/TicketHandler.cpp @@ -23,6 +23,7 @@ #include "Opcodes.h" #include "Player.h" #include "TicketMgr.h" +#include "TicketPackets.h" #include "Util.h" #include "World.h" #include "WorldPacket.h" @@ -156,13 +157,19 @@ void WorldSession::HandleGMTicketDeleteOpcode(WorldPacket & /*recvData*/) } } -void WorldSession::HandleGMTicketGetTicketOpcode(WorldPacket & /*recvData*/) +void WorldSession::HandleGMTicketGetCaseStatusOpcode(WorldPackets::Ticket::GMTicketGetCaseStatus& /*packet*/) +{ + // TODO: Implement GmCase and handle this packet correctly +} + +void WorldSession::HandleGMTicketGetTicketOpcode(WorldPackets::Ticket::GMTicketGetTicket& /*packet*/) { SendQueryTimeResponse(); if (GmTicket* ticket = sTicketMgr->GetTicketByPlayer(GetPlayer()->GetGUID())) { if (ticket->IsCompleted()) + // TODO: Update SMSG_GM_TICKET_RESPONSE ticket->SendResponse(this); else sTicketMgr->SendTicket(this, ticket); @@ -171,13 +178,13 @@ void WorldSession::HandleGMTicketGetTicketOpcode(WorldPacket & /*recvData*/) sTicketMgr->SendTicket(this, NULL); } -void WorldSession::HandleGMTicketSystemStatusOpcode(WorldPacket & /*recvData*/) +void WorldSession::HandleGMTicketSystemStatusOpcode(WorldPackets::Ticket::GMTicketGetSystemStatus& /*packet*/) { // Note: This only disables the ticket UI at client side and is not fully reliable - // are we sure this is a uint32? Should ask Zor - WorldPacket data(SMSG_GM_TICKET_SYSTEM_STATUS, 4); - data << uint32(sTicketMgr->GetStatus() ? GMTICKET_QUEUE_STATUS_ENABLED : GMTICKET_QUEUE_STATUS_DISABLED); - SendPacket(&data); + // Note: This disables the whole customer support UI after trying to send a ticket in disabled state (MessageBox: "GM Help Tickets are currently unavaiable."). UI remains disabled until the character relogs. + WorldPackets::Ticket::GMTicketSystemStatus response; + response.Status = sTicketMgr->GetStatus() ? GMTICKET_QUEUE_STATUS_ENABLED : GMTICKET_QUEUE_STATUS_DISABLED; + SendPacket(response.Write()); } void WorldSession::HandleGMSurveySubmit(WorldPacket& recvData) diff --git a/src/server/game/Server/Packets/TicketPackets.cpp b/src/server/game/Server/Packets/TicketPackets.cpp new file mode 100644 index 00000000000..8567cebd736 --- /dev/null +++ b/src/server/game/Server/Packets/TicketPackets.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "TicketPackets.h" + +WorldPacket const* WorldPackets::Ticket::GMTicketSystemStatus::Write() +{ + _worldPacket << int32(Status); + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::Ticket::GMTicketCaseStatus::Write() +{ + _worldPacket.AppendPackedTime(OldestTicketTime); + _worldPacket.AppendPackedTime(UpdateTime); + + _worldPacket << int32(Cases.size()); + + for (auto const& c : Cases) + { + _worldPacket << int32(c.CaseID); + _worldPacket << int32(c.CaseOpened); + _worldPacket << int32(c.CaseStatus); + _worldPacket << int16(c.CfgRealmID); + _worldPacket << int64(c.CharacterID); + _worldPacket << int32(c.WaitTimeOverrideMinutes); + + _worldPacket.WriteBits(c.Url.size(), 11); + _worldPacket.WriteBits(c.WaitTimeOverrideMessage.size(), 10); + } + + _worldPacket.FlushBits(); + return &_worldPacket; +} + +void WorldPackets::Ticket::GMTicketAcknowledgeSurvey::Read() +{ + _worldPacket >> CaseID; +} + +WorldPacket const* WorldPackets::Ticket::GMTicketGetTicketResponse::Write() +{ + _worldPacket << Result; + _worldPacket.WriteBit(Info.HasValue); + + if (Info.HasValue) + { + _worldPacket << int32(Info.Value.TicketID); + _worldPacket << uint8(Info.Value.Category); + _worldPacket.AppendPackedTime(Info.Value.TicketOpenTime); + _worldPacket.AppendPackedTime(Info.Value.OldestTicketTime); + _worldPacket.AppendPackedTime(Info.Value.UpdateTime); + _worldPacket << uint8(Info.Value.AssignedToGM); + _worldPacket << uint8(Info.Value.OpenedByGM); + _worldPacket << int32(Info.Value.WaitTimeOverrideMinutes); + + _worldPacket.WriteBits(Info.Value.TicketDescription.size(), 11); + _worldPacket.WriteBits(Info.Value.WaitTimeOverrideMessage.size(), 10); + + _worldPacket.WriteString(Info.Value.TicketDescription); + _worldPacket.WriteString(Info.Value.WaitTimeOverrideMessage); + } + + _worldPacket.FlushBits(); + + return &_worldPacket; +} diff --git a/src/server/game/Server/Packets/TicketPackets.h b/src/server/game/Server/Packets/TicketPackets.h new file mode 100644 index 00000000000..076db67fb96 --- /dev/null +++ b/src/server/game/Server/Packets/TicketPackets.h @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TicketPackets_h__ +#define TicketPackets_h__ + +#include "Packet.h" + +namespace WorldPackets +{ + namespace Ticket + { + class GMTicketGetSystemStatus final : public ClientPacket + { + public: + GMTicketGetSystemStatus(WorldPacket&& packet) : ClientPacket(CMSG_GM_TICKET_GET_SYSTEM_STATUS, std::move(packet)) { } + + void Read() override { }; + }; + + class GMTicketSystemStatus final : public ServerPacket + { + public: + GMTicketSystemStatus() : ServerPacket(SMSG_GM_TICKET_SYSTEM_STATUS, 4) { } + + WorldPacket const* Write() override; + + int32 Status = 0; + }; + + class GMTicketGetCaseStatus final : public ClientPacket + { + public: + GMTicketGetCaseStatus(WorldPacket&& packet) : ClientPacket(CMSG_GM_TICKET_GET_CASE_STATUS, std::move(packet)) { } + + void Read() override { }; + }; + + class GMTicketCaseStatus final : public ServerPacket + { + public: + struct GMTicketCase + { + int32 CaseID = 0; + int32 CaseOpened = 0; + int32 CaseStatus = 0; + int16 CfgRealmID = 0; + int64 CharacterID = 0; + int32 WaitTimeOverrideMinutes = 0; + std::string Url; + std::string WaitTimeOverrideMessage; + + bool operator<(GMTicketCase const& right) const + { + return CaseID < right.CaseID; + } + }; + + GMTicketCaseStatus() : ServerPacket(SMSG_GM_TICKET_CASE_STATUS, 12) { } + + WorldPacket const* Write() override; + + time_t OldestTicketTime = 0; + time_t UpdateTime = 0; + std::set<GMTicketCase> Cases; + }; + + class GMTicketGetTicket final : public ClientPacket + { + public: + GMTicketGetTicket(WorldPacket&& packet) : ClientPacket(CMSG_GM_TICKET_GET_TICKET, std::move(packet)) { } + + void Read() override { } + }; + + class GMTicketGetTicketResponse final : public ServerPacket + { + public: + struct GMTicketInfo + { + int32 TicketID = 0; + std::string TicketDescription; + uint8 Category = 0; + time_t TicketOpenTime = 0; + time_t OldestTicketTime = 0; + time_t UpdateTime = 0; + uint8 AssignedToGM = 0; + uint8 OpenedByGM = 0; + std::string WaitTimeOverrideMessage; + int32 WaitTimeOverrideMinutes = 0; + }; + + GMTicketGetTicketResponse() : ServerPacket(SMSG_GM_TICKET_GET_TICKET_RESPONSE, 5) { } + + WorldPacket const* Write() override; + + int32 Result = 0; + Optional<GMTicketInfo> Info; + }; + + class GMTicketAcknowledgeSurvey final : public ClientPacket + { + public: + GMTicketAcknowledgeSurvey(WorldPacket&& packet) : ClientPacket(CMSG_GM_TICKET_ACKNOWLEDGE_SURVEY, std::move(packet)) { } + + void Read() override; + + int32 CaseID; + }; + + class GMTicketResponseError final : public ServerPacket + { + public: + GMTicketResponseError() : ServerPacket(SMSG_GM_TICKET_RESPONSE_ERROR, 0) { } + + WorldPacket const* Write() { return &_worldPacket; } + }; + } +} + +#endif // TicketPackets_h__ diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index 0cdd9ffa1fe..0dfb5b9d016 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -40,6 +40,7 @@ #include "Packets/QuestPackets.h" #include "Packets/TalentPackets.h" #include "Packets/TradePackets.h" +#include "Packets/TicketPackets.h" template<class PacketClass, void(WorldSession::*HandlerFunction)(PacketClass&)> class PacketHandler : public ClientOpcodeHandler @@ -387,11 +388,13 @@ void OpcodeTable::Initialize() DEFINE_OPCODE_HANDLER_OLD(CMSG_GM_SET_SECURITY_GROUP, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL ); DEFINE_OPCODE_HANDLER_OLD(CMSG_GM_SURVEY_SUBMIT, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::HandleGMSurveySubmit ); DEFINE_OPCODE_HANDLER_OLD(CMSG_GM_TICKET_ACKNOWLEDGE_SURVEY, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //DEFINE_HANDLER(CMSG_GM_TICKET_ACKNOWLEDGE_SURVEY, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Ticket::GMTicketAcknowledgeSurvey, &WorldSession::HandleGMTicketAcknowledgeSurveyOpcode); DEFINE_OPCODE_HANDLER_OLD(CMSG_GM_TICKET_CREATE, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::HandleGMTicketCreateOpcode ); DEFINE_OPCODE_HANDLER_OLD(CMSG_GM_TICKET_DELETE_TICKET, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::HandleGMTicketDeleteOpcode ); DEFINE_OPCODE_HANDLER_OLD(CMSG_GM_TICKET_GET_CASE_STATUS, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL ); - DEFINE_OPCODE_HANDLER_OLD(CMSG_GM_TICKET_GET_SYSTEM_STATUS, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::HandleGMTicketSystemStatusOpcode); - DEFINE_OPCODE_HANDLER_OLD(CMSG_GM_TICKET_GET_TICKET, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::HandleGMTicketGetTicketOpcode ); + //DEFINE_HANDLER(CMSG_GM_TICKET_GET_CASE_STATUS, STATUS_LOGGEDIN, PROCESS_INPLACE, WorldPackets::Ticket::GMTicketGetCaseStatus, &WorldSession::HandleGMTicketGetCaseStatusOpcode); + DEFINE_HANDLER(CMSG_GM_TICKET_GET_SYSTEM_STATUS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::Ticket::GMTicketGetSystemStatus, &WorldSession::HandleGMTicketSystemStatusOpcode); + DEFINE_HANDLER(CMSG_GM_TICKET_GET_TICKET, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::Ticket::GMTicketGetTicket, &WorldSession::HandleGMTicketGetTicketOpcode); DEFINE_OPCODE_HANDLER_OLD(CMSG_GM_TICKET_RESPONSE_RESOLVE, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::HandleGMResponseResolve ); DEFINE_OPCODE_HANDLER_OLD(CMSG_GM_TICKET_UPDATE_TEXT, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::HandleGMTicketUpdateOpcode ); DEFINE_HANDLER(CMSG_GOSSIP_HELLO, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::NPC::Hello, &WorldSession::HandleGossipHelloOpcode); @@ -1277,12 +1280,12 @@ void OpcodeTable::Initialize() DEFINE_SERVER_OPCODE_HANDLER(SMSG_GM_PLAYER_INFO, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_GM_REQUEST_PLAYER_INFO, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_GM_TICKET_CASE_STATUS, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_GM_TICKET_GET_TICKET_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_GM_TICKET_GET_TICKET_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_GM_TICKET_RESOLVE_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_GM_TICKET_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_GM_TICKET_RESPONSE_ERROR, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_GM_TICKET_STATUS_UPDATE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_GM_TICKET_SYSTEM_STATUS, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_GM_TICKET_SYSTEM_STATUS, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_GM_TICKET_UPDATE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_GOD_MODE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_GOSSIP_COMPLETE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index a97acc23ead..2b54b5d794d 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -267,6 +267,14 @@ namespace WorldPackets class LearnTalents; } + namespace Ticket + { + class GMTicketGetSystemStatus; + class GMTicketGetCaseStatus; + class GMTicketGetTicket; + class GMTicketAcknowledgeSurvey; + } + namespace Trade { class CancelTrade; @@ -510,6 +518,7 @@ class WorldSession void SendAuthWaitQue(uint32 position); void SendSetTimeZoneInformation(); + void SendFeatureSystemStatus(); void SendFeatureSystemStatusGlueScreen(); void SendNameQueryOpcode(ObjectGuid guid); @@ -736,8 +745,9 @@ class WorldSession void HandleGMTicketCreateOpcode(WorldPacket& recvPacket); void HandleGMTicketUpdateOpcode(WorldPacket& recvPacket); void HandleGMTicketDeleteOpcode(WorldPacket& recvPacket); - void HandleGMTicketGetTicketOpcode(WorldPacket& recvPacket); - void HandleGMTicketSystemStatusOpcode(WorldPacket& recvPacket); + void HandleGMTicketGetCaseStatusOpcode(WorldPackets::Ticket::GMTicketGetCaseStatus& packet); + void HandleGMTicketGetTicketOpcode(WorldPackets::Ticket::GMTicketGetTicket& packet); + void HandleGMTicketSystemStatusOpcode(WorldPackets::Ticket::GMTicketGetSystemStatus& packet); void HandleGMSurveySubmit(WorldPacket& recvPacket); void HandleReportLag(WorldPacket& recvPacket); void HandleGMResponseResolve(WorldPacket& recvPacket); diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 7c5ab810ce4..4e52adf9944 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -5769,7 +5769,7 @@ void Spell::EffectCreateAreaTrigger(SpellEffIndex /*effIndex*/) delete areaTrigger; } -void Spell::EffectRemoveTalent(SpellEffIndex effIndex) +void Spell::EffectRemoveTalent(SpellEffIndex /*effIndex*/) { if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) return; diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index d0eff5fff00..5dd41eb9bcf 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -3072,6 +3072,8 @@ void SpellMgr::LoadSpellInfoCorrections() case 45976: // Muru Portal Channel case 52124: // Sky Darkener Assault case 53096: // Quetz'lun's Judgment + case 70743: // AoD Special + case 70614: // AoD Special - Vegard case 52479: // Gift of the Harvester case 61588: // Blazing Harpoon spellInfo->MaxAffectedTargets = 1; diff --git a/src/server/game/Tickets/TicketMgr.cpp b/src/server/game/Tickets/TicketMgr.cpp index 7cd27fdaa62..84dac6cc175 100644 --- a/src/server/game/Tickets/TicketMgr.cpp +++ b/src/server/game/Tickets/TicketMgr.cpp @@ -18,6 +18,7 @@ #include "Common.h" #include "TicketMgr.h" +#include "TicketPackets.h" #include "DatabaseEnv.h" #include "Log.h" #include "Language.h" @@ -28,7 +29,7 @@ #include "Player.h" #include "Opcodes.h" -inline float GetAge(uint64 t) { return float(time(NULL) - t) / DAY; } +inline time_t GetAge(uint64 t) { return (time(NULL) - t) / DAY; } /////////////////////////////////////////////////////////////////////////////////////////////////// // GM ticket @@ -113,30 +114,6 @@ void GmTicket::DeleteFromDB() CharacterDatabase.Execute(stmt); } -void GmTicket::WritePacket(WorldPacket& data) const -{ - data << uint32(GMTICKET_STATUS_HASTEXT); - data << uint32(_id); - data << _message; - data << uint8(_needMoreHelp); - data << GetAge(_lastModifiedTime); - if (GmTicket* ticket = sTicketMgr->GetOldestOpenTicket()) - data << GetAge(ticket->GetLastModifiedTime()); - else - data << float(0); - - // I am not sure how blizzlike this is, and we don't really have a way to find out - data << GetAge(sTicketMgr->GetLastChange()); - - data << uint8(std::min(_escalatedStatus, TICKET_IN_ESCALATION_QUEUE)); // escalated data - data << uint8(_viewed ? GMTICKET_OPENEDBYGM_STATUS_OPENED : GMTICKET_OPENEDBYGM_STATUS_NOT_OPENED); // whether or not it has been viewed - - // TODO: implement these - std::string waitTimeOverrideMessage = ""; - data << waitTimeOverrideMessage; - data << uint32(0); // waitTimeOverrideMinutes -} - void GmTicket::SendResponse(WorldSession* session) const { WorldPacket data(SMSG_GM_TICKET_RESPONSE); @@ -267,7 +244,7 @@ TicketMgr::~TicketMgr() void TicketMgr::Initialize() { - SetStatus(sWorld->getBoolConfig(CONFIG_ALLOW_TICKETS)); + SetStatus(sWorld->getBoolConfig(CONFIG_TICKET_SYSTEM_STATUS)); } void TicketMgr::ResetTickets() @@ -408,12 +385,26 @@ void TicketMgr::ShowEscalatedList(ChatHandler& handler) const void TicketMgr::SendTicket(WorldSession* session, GmTicket* ticket) const { - WorldPacket data(SMSG_GM_TICKET_GET_TICKET_RESPONSE, (ticket ? (4 + 4 + 1 + 4 + 4 + 4 + 1 + 1) : 4)); + WorldPackets::Ticket::GMTicketGetTicketResponse response; if (ticket) - ticket->WritePacket(data); + { + response.Result = GMTICKET_STATUS_HASTEXT; + response.Info.HasValue = true; + + response.Info.Value.TicketID = ticket->GetId(); + response.Info.Value.TicketDescription = ticket->GetMessage(); + response.Info.Value.Category = ticket->GetNeedMoreHelp(); + response.Info.Value.TicketOpenTime = GetAge(ticket->GetLastModifiedTime()); + response.Info.Value.OldestTicketTime = sTicketMgr->GetOldestOpenTicket() ? GetAge(sTicketMgr->GetOldestOpenTicket()->GetLastModifiedTime()) : float(0); + response.Info.Value.UpdateTime = GetAge(sTicketMgr->GetLastChange()); + response.Info.Value.AssignedToGM = ticket->IsAssigned(); + response.Info.Value.OpenedByGM = ticket->IsViewed(); + response.Info.Value.WaitTimeOverrideMessage = ""; + response.Info.Value.WaitTimeOverrideMinutes = 0; + } else - data << uint32(GMTICKET_STATUS_DEFAULT); + response.Result = GMTICKET_STATUS_DEFAULT; - session->SendPacket(&data); + session->SendPacket(response.Write()); } diff --git a/src/server/game/Tickets/TicketMgr.h b/src/server/game/Tickets/TicketMgr.h index 4dd94cc1762..578d93bb128 100644 --- a/src/server/game/Tickets/TicketMgr.h +++ b/src/server/game/Tickets/TicketMgr.h @@ -90,11 +90,13 @@ public: bool IsAssigned() const { return !_assignedTo.IsEmpty(); } bool IsAssignedTo(ObjectGuid guid) const { return guid == _assignedTo; } bool IsAssignedNotTo(ObjectGuid guid) const { return IsAssigned() && !IsAssignedTo(guid); } + bool IsViewed() const { return _viewed; } uint32 GetId() const { return _id; } Player* GetPlayer() const { return ObjectAccessor::FindPlayer(_playerGuid); } std::string const& GetPlayerName() const { return _playerName; } std::string const& GetMessage() const { return _message; } + bool const GetNeedMoreHelp() const { return _needMoreHelp; } Player* GetAssignedPlayer() const { return ObjectAccessor::FindPlayer(_assignedTo); } ObjectGuid GetAssignedToGUID() const { return _assignedTo; } std::string GetAssignedToName() const diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index fe3c4b76167..7885819af9f 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -278,6 +278,7 @@ void World::AddSession_(WorldSession* s) s->SendAuthResponse(AUTH_OK, false); s->SendSetTimeZoneInformation(); + s->SendFeatureSystemStatus(); s->SendFeatureSystemStatusGlueScreen(); s->SendAddonsInfo(); @@ -377,6 +378,7 @@ bool World::RemoveQueuedPlayer(WorldSession* sess) pop_sess->SendAuthWaitQue(0); pop_sess->SendSetTimeZoneInformation(); + pop_sess->SendFeatureSystemStatus(); pop_sess->SendFeatureSystemStatusGlueScreen(); pop_sess->SendAddonsInfo(); @@ -428,7 +430,11 @@ void World::LoadConfigSettings(bool reload) SetMotd(sConfigMgr->GetStringDefault("Motd", "Welcome to a Trinity Core Server.")); ///- Read ticket system setting from the config file - m_bool_configs[CONFIG_ALLOW_TICKETS] = sConfigMgr->GetBoolDefault("AllowTickets", true); + m_bool_configs[CONFIG_TICKET_SYSTEM_STATUS] = sConfigMgr->GetBoolDefault("Ticket.SystemStatus", true); + if (reload) + sTicketMgr->SetStatus(m_bool_configs[CONFIG_TICKET_SYSTEM_STATUS]); + m_bool_configs[CONFIG_TICKET_SUBMIT_TICKET] = sConfigMgr->GetBoolDefault("Ticket.SubmitTicket", false); + m_bool_configs[CONFIG_TICKET_SUBMIT_BUG] = sConfigMgr->GetBoolDefault("Ticket.SubmitBug", false); ///- Get string for new logins (newly created characters) SetNewCharString(sConfigMgr->GetStringDefault("PlayerStart.String", "")); diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index 2526516d778..c30d4f6ca35 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -151,7 +151,9 @@ enum WorldBoolConfigs CONFIG_SHOW_MUTE_IN_WORLD, CONFIG_SHOW_BAN_IN_WORLD, CONFIG_AUTOBROADCAST, - CONFIG_ALLOW_TICKETS, + CONFIG_TICKET_SYSTEM_STATUS, + CONFIG_TICKET_SUBMIT_TICKET, + CONFIG_TICKET_SUBMIT_BUG, CONFIG_DBC_ENFORCE_ITEM_ATTRIBUTES, CONFIG_PRESERVE_CUSTOM_CHANNELS, CONFIG_PDUMP_NO_PATHS, diff --git a/src/server/shared/DataStores/DB2Store.h b/src/server/shared/DataStores/DB2Store.h index c4ca929065b..9514f34b294 100644 --- a/src/server/shared/DataStores/DB2Store.h +++ b/src/server/shared/DataStores/DB2Store.h @@ -80,8 +80,7 @@ void WriteDB2RecordToPacket(DB2Storage<T> const& store, uint32 id, uint32 locale char const* str = locStr->Str[locale]; size_t len = strlen(str); buffer << uint16(len); - if (len) - buffer << str; + buffer.WriteString(str, len); entry += sizeof(char*); break; } diff --git a/src/server/shared/Database/DatabaseWorkerPool.h b/src/server/shared/Database/DatabaseWorkerPool.h index 5a06ad69a1d..f0ddbe91ad8 100644 --- a/src/server/shared/Database/DatabaseWorkerPool.h +++ b/src/server/shared/Database/DatabaseWorkerPool.h @@ -29,6 +29,8 @@ #include "QueryHolder.h" #include "AdhocStatement.h" +#include <mysqld_error.h> + #define MIN_MYSQL_SERVER_VERSION 50100u #define MIN_MYSQL_CLIENT_VERSION 50100u @@ -368,7 +370,8 @@ class DatabaseWorkerPool void DirectCommitTransaction(SQLTransaction& transaction) { T* con = GetFreeConnection(); - if (con->ExecuteTransaction(transaction)) + int errorCode = con->ExecuteTransaction(transaction); + if (!errorCode) { con->Unlock(); // OK, operation succesful return; @@ -376,12 +379,12 @@ class DatabaseWorkerPool //! Handle MySQL Errno 1213 without extending deadlock to the core itself /// @todo More elegant way - if (con->GetLastError() == 1213) + if (errorCode == ER_LOCK_DEADLOCK) { uint8 loopBreaker = 5; for (uint8 i = 0; i < loopBreaker; ++i) { - if (con->ExecuteTransaction(transaction)) + if (!con->ExecuteTransaction(transaction)) break; } } diff --git a/src/server/shared/Database/MySQLConnection.cpp b/src/server/shared/Database/MySQLConnection.cpp index bea229df184..1a9f973d47b 100644 --- a/src/server/shared/Database/MySQLConnection.cpp +++ b/src/server/shared/Database/MySQLConnection.cpp @@ -359,11 +359,11 @@ void MySQLConnection::CommitTransaction() Execute("COMMIT"); } -bool MySQLConnection::ExecuteTransaction(SQLTransaction& transaction) +int MySQLConnection::ExecuteTransaction(SQLTransaction& transaction) { std::list<SQLElementData> const& queries = transaction->m_queries; if (queries.empty()) - return false; + return -1; BeginTransaction(); @@ -380,8 +380,9 @@ bool MySQLConnection::ExecuteTransaction(SQLTransaction& transaction) if (!Execute(stmt)) { TC_LOG_WARN("sql.sql", "Transaction aborted. %u queries not executed.", (uint32)queries.size()); + int errorCode = GetLastError(); RollbackTransaction(); - return false; + return errorCode; } } break; @@ -392,8 +393,9 @@ bool MySQLConnection::ExecuteTransaction(SQLTransaction& transaction) if (!Execute(sql)) { TC_LOG_WARN("sql.sql", "Transaction aborted. %u queries not executed.", (uint32)queries.size()); + int errorCode = GetLastError(); RollbackTransaction(); - return false; + return errorCode; } } break; @@ -406,7 +408,7 @@ bool MySQLConnection::ExecuteTransaction(SQLTransaction& transaction) // and not while iterating over every element. CommitTransaction(); - return true; + return 0; } MySQLPreparedStatement* MySQLConnection::GetPreparedStatement(uint32 index) diff --git a/src/server/shared/Database/MySQLConnection.h b/src/server/shared/Database/MySQLConnection.h index 33f17d02228..d486f5b4679 100644 --- a/src/server/shared/Database/MySQLConnection.h +++ b/src/server/shared/Database/MySQLConnection.h @@ -86,7 +86,7 @@ class MySQLConnection void BeginTransaction(); void RollbackTransaction(); void CommitTransaction(); - bool ExecuteTransaction(SQLTransaction& transaction); + int ExecuteTransaction(SQLTransaction& transaction); operator bool () const { return m_Mysql != NULL; } void Ping() { mysql_ping(m_Mysql); } diff --git a/src/server/shared/Database/Transaction.cpp b/src/server/shared/Database/Transaction.cpp index 3dee865267b..b83b787a106 100644 --- a/src/server/shared/Database/Transaction.cpp +++ b/src/server/shared/Database/Transaction.cpp @@ -17,6 +17,7 @@ #include "DatabaseEnv.h" #include "Transaction.h" +#include <mysqld_error.h> //- Append a raw ad-hoc query to the transaction void Transaction::Append(const char* sql) @@ -74,14 +75,15 @@ void Transaction::Cleanup() bool TransactionTask::Execute() { - if (m_conn->ExecuteTransaction(m_trans)) + int errorCode = m_conn->ExecuteTransaction(m_trans); + if (!errorCode) return true; - if (m_conn->GetLastError() == 1213) + if (errorCode == ER_LOCK_DEADLOCK) { uint8 loopBreaker = 5; // Handle MySQL Errno 1213 without extending deadlock to the core itself for (uint8 i = 0; i < loopBreaker; ++i) - if (m_conn->ExecuteTransaction(m_trans)) + if (!m_conn->ExecuteTransaction(m_trans)) return true; } diff --git a/src/server/shared/Packets/ByteBuffer.h b/src/server/shared/Packets/ByteBuffer.h index 1063883d217..afa10207321 100644 --- a/src/server/shared/Packets/ByteBuffer.h +++ b/src/server/shared/Packets/ByteBuffer.h @@ -544,6 +544,12 @@ class ByteBuffer append(str.c_str(), len); } + void WriteString(char const* str, size_t len) + { + if (len) + append(str, len); + } + uint32 ReadPackedTime() { uint32 packedDate = read<uint32>(); diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index 9cb25475ecf..0327d0f8884 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -1060,12 +1060,31 @@ Server.LoginInfo = 0 Command.LookupMaxResults = 0 # -# AllowTickets -# Description: Allow/disallow sending new tickets. +# Ticket.SystemStatus +# Description: Enable/disable the ticket system. This disables the whole customer +# support UI after trying to send a ticket in disabled state +# (MessageBox: "GM Help Tickets are currently unavailable."). +# UI remains disabled until the character relogs. # Default: 1 - (Enabled) # 0 - (Disabled) -AllowTickets = 1 +Ticket.SystemStatus = 1 + +# +# Ticket.SubmitTicket +# Description: Allow/disallow opening new tickets. +# Default: 0 - (Disabled) +# 1 - (Enabled, Experimental) + +Ticket.SubmitTicket = 0 + +# +# Ticket.SubmitBug +# Description: Allow/disallow opening new bug reports. +# Default: 0 - (Disabled) +# 1 - (Enabled, Experimental) + +Ticket.SubmitBug = 0 # # DungeonFinder.OptionsMask |
