diff options
Diffstat (limited to 'src')
55 files changed, 1102 insertions, 728 deletions
diff --git a/src/server/authserver/Server/AuthSession.cpp b/src/server/authserver/Server/AuthSession.cpp index 60e9b734b13..acab29a5b86 100644 --- a/src/server/authserver/Server/AuthSession.cpp +++ b/src/server/authserver/Server/AuthSession.cpp @@ -43,12 +43,6 @@ enum eAuthCmd XFER_CANCEL = 0x34 }; -enum eStatus -{ - STATUS_CONNECTED = 0, - STATUS_AUTHED -}; - #pragma pack(push, 1) typedef struct AUTH_LOGON_CHALLENGE_C @@ -115,11 +109,10 @@ enum class BufferSizes : uint32 SRP_6_S = 0x20, }; +#define MAX_ACCEPTED_CHALLENGE_SIZE (sizeof(AUTH_LOGON_CHALLENGE_C) + 16) + #define AUTH_LOGON_CHALLENGE_INITIAL_SIZE 4 #define REALM_LIST_PACKET_SIZE 5 -#define XFER_ACCEPT_SIZE 1 -#define XFER_RESUME_SIZE 9 -#define XFER_CANCEL_SIZE 1 std::unordered_map<uint8, AuthHandler> AuthSession::InitHandlers() { @@ -130,15 +123,102 @@ std::unordered_map<uint8, AuthHandler> AuthSession::InitHandlers() handlers[AUTH_RECONNECT_CHALLENGE] = { STATUS_CONNECTED, AUTH_LOGON_CHALLENGE_INITIAL_SIZE, &AuthSession::HandleReconnectChallenge }; handlers[AUTH_RECONNECT_PROOF] = { STATUS_CONNECTED, sizeof(AUTH_RECONNECT_PROOF_C), &AuthSession::HandleReconnectProof }; handlers[REALM_LIST] = { STATUS_AUTHED, REALM_LIST_PACKET_SIZE, &AuthSession::HandleRealmList }; - handlers[XFER_ACCEPT] = { STATUS_AUTHED, XFER_ACCEPT_SIZE, &AuthSession::HandleXferAccept }; - handlers[XFER_RESUME] = { STATUS_AUTHED, XFER_RESUME_SIZE, &AuthSession::HandleXferResume }; - handlers[XFER_CANCEL] = { STATUS_AUTHED, XFER_CANCEL_SIZE, &AuthSession::HandleXferCancel }; return handlers; } std::unordered_map<uint8, AuthHandler> const Handlers = AuthSession::InitHandlers(); +void AccountInfo::LoadResult(Field* fields) +{ + // 0 1 2 3 4 5 6 + //SELECT a.id, a.username, a.locked, a.lock_country, a.last_ip, a.failed_logins, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, + // 7 8 9 10 11 12 + // ab.unbandate = ab.bandate, aa.gmlevel, a.token_key, a.sha_pass_hash, a.v, a.s + //FROM account a LEFT JOIN account_access aa ON a.id = aa.id LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 WHERE a.username = ? + + Id = fields[0].GetUInt32(); + Login = fields[1].GetString(); + IsLockedToIP = fields[2].GetBool(); + LockCountry = fields[3].GetString(); + LastIP = fields[4].GetString(); + FailedLogins = fields[5].GetUInt32(); + IsBanned = fields[6].GetUInt64() != 0; + IsPermanenetlyBanned = fields[7].GetUInt64() != 0; + SecurityLevel = AccountTypes(fields[8].GetUInt8()); +} + +AuthSession::AuthSession(tcp::socket&& socket) : Socket(std::move(socket)), +_sentChallenge(false), _sentProof(false), +_status(STATUS_CONNECTED), _build(0), _expversion(0) +{ + N.SetHexStr("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7"); + g.SetDword(7); +} + +void AuthSession::Start() +{ + std::string ip_address = GetRemoteIpAddress().to_string(); + TC_LOG_TRACE("session", "Accepted connection from %s", ip_address.c_str()); + + // Remove expired ip ban if needed - login might fail for the first time + // but its better than allowing ourselves to be flooded by connections triggering blocking queries + LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_DEL_EXPIRED_IP_BANS)); + + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_IP_INFO); + stmt->setString(0, ip_address); + stmt->setUInt32(1, inet_addr(ip_address.c_str())); + + _queryCallback = std::bind(&AuthSession::CheckIpCallback, this, std::placeholders::_1); + _queryFuture = LoginDatabase.AsyncQuery(stmt); +} + +bool AuthSession::Update() +{ + if (!AuthSocket::Update()) + return false; + + if (_queryFuture.valid() && _queryFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready) + { + auto callback = _queryCallback; + _queryCallback = nullptr; + callback(_queryFuture.get()); + } + + return true; +} + +void AuthSession::CheckIpCallback(PreparedQueryResult result) +{ + if (result) + { + bool banned = false; + do + { + Field* fields = result->Fetch(); + if (fields[0].GetUInt64() != 0) + banned = true; + + if (!fields[1].GetString().empty()) + _ipCountry = fields[1].GetString(); + + } while (result->NextRow()); + + if (banned) + { + ByteBuffer pkt; + pkt << uint8(AUTH_LOGON_CHALLENGE); + pkt << uint8(0x00); + pkt << uint8(WOW_FAIL_BANNED); + SendPacket(pkt); + TC_LOG_DEBUG("session", "[AuthSession::CheckIpCallback] Banned ip '%s:%d' tries to login!", GetRemoteIpAddress().to_string().c_str(), GetRemotePort()); + return; + } + } + + AsyncRead(); +} + void AuthSession::ReadHandler() { MessageBuffer& packet = GetReadBuffer(); @@ -153,6 +233,12 @@ void AuthSession::ReadHandler() break; } + if (_status != itr->second.status) + { + CloseSocket(); + return; + } + uint16 size = uint16(itr->second.packetSize); if (packet.GetActiveSize() < size) break; @@ -161,12 +247,17 @@ void AuthSession::ReadHandler() { sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(packet.GetReadPointer()); size += challenge->size; + if (size > MAX_ACCEPTED_CHALLENGE_SIZE) + { + CloseSocket(); + return; + } } if (packet.GetActiveSize() < size) break; - if (!(*this.*Handlers.at(cmd).handler)()) + if (!(*this.*itr->second.handler)()) { CloseSocket(); return; @@ -196,224 +287,206 @@ void AuthSession::SendPacket(ByteBuffer& packet) bool AuthSession::HandleLogonChallenge() { + if (_sentChallenge) + return false; + + _sentChallenge = true; + sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(GetReadBuffer().GetReadPointer()); + if (challenge->size - (sizeof(sAuthLogonChallenge_C) - AUTH_LOGON_CHALLENGE_INITIAL_SIZE - 1) != challenge->I_len) + return false; - //TC_LOG_DEBUG("server.authserver", "[AuthChallenge] got full packet, %#04x bytes", challenge->size); - TC_LOG_DEBUG("server.authserver", "[AuthChallenge] name(%d): '%s'", challenge->I_len, challenge->I); + std::string login((const char*)challenge->I, challenge->I_len); + TC_LOG_DEBUG("server.authserver", "[AuthChallenge] '%s'", login.c_str()); - ByteBuffer pkt; + if (_queryCallback) + { + ByteBuffer pkt; + pkt << uint8(AUTH_LOGON_CHALLENGE); + pkt << uint8(0x00); + pkt << uint8(WOW_FAIL_DB_BUSY); + SendPacket(pkt); + + TC_LOG_DEBUG("server.authserver", "[AuthChallenge] %s attempted to log too quick after previous attempt!", login.c_str()); + return true; + } - _login.assign((const char*)challenge->I, challenge->I_len); _build = challenge->build; _expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG)); - _os = (const char*)challenge->os; - - if (_os.size() > 4) - return false; + std::array<char, 5> os; + os.fill('\0'); + memcpy(os.data(), challenge->os, sizeof(challenge->os)); + _os = os.data(); // Restore string order as its byte order is reversed std::reverse(_os.begin(), _os.end()); + _localizationName.resize(4); + for (int i = 0; i < 4; ++i) + _localizationName[i] = challenge->country[4 - i - 1]; + + // Get the account details from the account table + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LOGONCHALLENGE); + stmt->setString(0, login); + + _queryCallback = std::bind(&AuthSession::LogonChallengeCallback, this, std::placeholders::_1); + _queryFuture = LoginDatabase.AsyncQuery(stmt); + return true; +} + +void AuthSession::LogonChallengeCallback(PreparedQueryResult result) +{ + ByteBuffer pkt; pkt << uint8(AUTH_LOGON_CHALLENGE); pkt << uint8(0x00); - // Verify that this IP is not in the ip_banned table - LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_DEL_EXPIRED_IP_BANS)); + if (!result) + { + pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT); + SendPacket(pkt); + return; + } + + Field* fields = result->Fetch(); + + _accountInfo.LoadResult(fields); std::string ipAddress = GetRemoteIpAddress().to_string(); uint16 port = GetRemotePort(); - PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_IP_BANNED); - stmt->setString(0, ipAddress); - PreparedQueryResult result = LoginDatabase.Query(stmt); - if (result) + // If the IP is 'locked', check that the player comes indeed from the correct IP address + if (_accountInfo.IsLockedToIP) { - pkt << uint8(WOW_FAIL_BANNED); - TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] Banned ip tries to login!", ipAddress.c_str(), port); + TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is locked to IP - '%s' is logging in from '%s'", _accountInfo.Login.c_str(), _accountInfo.LastIP.c_str(), ipAddress.c_str()); + if (_accountInfo.LastIP != ipAddress) + { + pkt << uint8(WOW_FAIL_LOCKED_ENFORCED); + SendPacket(pkt); + return; + } } else { - // Get the account details from the account table - // No SQL injection (prepared statement) - stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LOGONCHALLENGE); - stmt->setString(0, _login); - - PreparedQueryResult res2 = LoginDatabase.Query(stmt); - if (res2) + TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is not locked to ip", _accountInfo.Login.c_str()); + if (_accountInfo.LockCountry.empty() || _accountInfo.LockCountry == "00") + TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is not locked to country", _accountInfo.Login.c_str()); + else if (!_accountInfo.LockCountry.empty() && !_ipCountry.empty()) { - Field* fields = res2->Fetch(); - - // If the IP is 'locked', check that the player comes indeed from the correct IP address - bool locked = false; - if (fields[2].GetUInt8() == 1) // if ip is locked - { - TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is locked to IP - '%s'", _login.c_str(), fields[4].GetCString()); - TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Player address is '%s'", ipAddress.c_str()); - - if (strcmp(fields[4].GetCString(), ipAddress.c_str()) != 0) - { - TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account IP differs"); - pkt << uint8(WOW_FAIL_LOCKED_ENFORCED); - locked = true; - } - else - TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account IP matches"); - } - else + TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is locked to country: '%s' Player country is '%s'", _accountInfo.Login.c_str(), _accountInfo.LockCountry.c_str(), _ipCountry.c_str()); + if (_ipCountry != _accountInfo.LockCountry) { - TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is not locked to ip", _login.c_str()); - std::string accountCountry = fields[3].GetString(); - if (accountCountry.empty() || accountCountry == "00") - TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is not locked to country", _login.c_str()); - else if (!accountCountry.empty()) - { - uint32 ip = inet_addr(ipAddress.c_str()); - EndianConvertReverse(ip); - - stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LOGON_COUNTRY); - stmt->setUInt32(0, ip); - if (PreparedQueryResult sessionCountryQuery = LoginDatabase.Query(stmt)) - { - std::string loginCountry = (*sessionCountryQuery)[0].GetString(); - TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is locked to country: '%s' Player country is '%s'", _login.c_str(), - accountCountry.c_str(), loginCountry.c_str()); - - if (loginCountry != accountCountry) - { - TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account country differs."); - pkt << uint8(WOW_FAIL_UNLOCKABLE_LOCK); - locked = true; - } - else - TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account country matches"); - } - else - TC_LOG_DEBUG("server.authserver", "[AuthChallenge] IP2NATION Table empty"); - } + pkt << uint8(WOW_FAIL_UNLOCKABLE_LOCK); + SendPacket(pkt); + return; } + } + } - if (!locked) - { - //set expired bans to inactive - LoginDatabase.DirectExecute(LoginDatabase.GetPreparedStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS)); - - // If the account is banned, reject the logon attempt - stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_BANNED); - stmt->setUInt32(0, fields[1].GetUInt32()); - PreparedQueryResult banresult = LoginDatabase.Query(stmt); - if (banresult) - { - if ((*banresult)[0].GetUInt32() == (*banresult)[1].GetUInt32()) - { - pkt << uint8(WOW_FAIL_BANNED); - TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] Banned account %s tried to login!", ipAddress.c_str(), - port, _login.c_str()); - } - else - { - pkt << uint8(WOW_FAIL_SUSPENDED); - TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] Temporarily banned account %s tried to login!", - ipAddress.c_str(), port, _login.c_str()); - } - } - else - { - // Get the password from the account table, upper it, and make the SRP6 calculation - std::string rI = fields[0].GetString(); - - // Don't calculate (v, s) if there are already some in the database - std::string databaseV = fields[6].GetString(); - std::string databaseS = fields[7].GetString(); - - TC_LOG_DEBUG("network", "database authentication values: v='%s' s='%s'", databaseV.c_str(), databaseS.c_str()); - - // multiply with 2 since bytes are stored as hexstring - if (databaseV.size() != size_t(BufferSizes::SRP_6_V) * 2 || databaseS.size() != size_t(BufferSizes::SRP_6_S) * 2) - SetVSFields(rI); - else - { - s.SetHexStr(databaseS.c_str()); - v.SetHexStr(databaseV.c_str()); - } - - b.SetRand(19 * 8); - BigNumber gmod = g.ModExp(b, N); - B = ((v * 3) + gmod) % N; - - ASSERT(gmod.GetNumBytes() <= 32); - - BigNumber unk3; - unk3.SetRand(16 * 8); - - // Fill the response packet with the result - if (AuthHelper::IsAcceptedClientBuild(_build)) - pkt << uint8(WOW_SUCCESS); - else - pkt << uint8(WOW_FAIL_VERSION_INVALID); - - // B may be calculated < 32B so we force minimal length to 32B - pkt.append(B.AsByteArray(32).get(), 32); // 32 bytes - pkt << uint8(1); - pkt.append(g.AsByteArray(1).get(), 1); - pkt << uint8(32); - pkt.append(N.AsByteArray(32).get(), 32); - pkt.append(s.AsByteArray(int32(BufferSizes::SRP_6_S)).get(), size_t(BufferSizes::SRP_6_S)); // 32 bytes - pkt.append(unk3.AsByteArray(16).get(), 16); - uint8 securityFlags = 0; - - // Check if token is used - _tokenKey = fields[8].GetString(); - if (!_tokenKey.empty()) - securityFlags = 4; - - pkt << uint8(securityFlags); // security flags (0x0...0x04) - - if (securityFlags & 0x01) // PIN input - { - pkt << uint32(0); - pkt << uint64(0) << uint64(0); // 16 bytes hash? - } - - if (securityFlags & 0x02) // Matrix input - { - pkt << uint8(0); - pkt << uint8(0); - pkt << uint8(0); - pkt << uint8(0); - pkt << uint64(0); - } - - if (securityFlags & 0x04) // Security token input - pkt << uint8(1); - - uint8 secLevel = fields[5].GetUInt8(); - _accountSecurityLevel = secLevel <= SEC_ADMINISTRATOR ? AccountTypes(secLevel) : SEC_ADMINISTRATOR; - - _localizationName.resize(4); - for (int i = 0; i < 4; ++i) - _localizationName[i] = challenge->country[4 - i - 1]; - - TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] account %s is using '%c%c%c%c' locale (%u)", - ipAddress.c_str(), port, _login.c_str(), - challenge->country[3], challenge->country[2], challenge->country[1], challenge->country[0], - GetLocaleByName(_localizationName) - ); - } - } + //set expired bans to inactive + LoginDatabase.DirectExecute(LoginDatabase.GetPreparedStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS)); + + // If the account is banned, reject the logon attempt + if (_accountInfo.IsBanned) + { + if (_accountInfo.IsPermanenetlyBanned) + { + pkt << uint8(WOW_FAIL_BANNED); + SendPacket(pkt); + TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] Banned account %s tried to login!", ipAddress.c_str(), port, _accountInfo.Login.c_str()); + return; + } + else + { + pkt << uint8(WOW_FAIL_SUSPENDED); + SendPacket(pkt); + TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] Temporarily banned account %s tried to login!", ipAddress.c_str(), port, _accountInfo.Login.c_str()); + return; } - else //no account - pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT); } + // Get the password from the account table, upper it, and make the SRP6 calculation + std::string rI = fields[10].GetString(); + + // Don't calculate (v, s) if there are already some in the database + std::string databaseV = fields[11].GetString(); + std::string databaseS = fields[12].GetString(); + + TC_LOG_DEBUG("network", "database authentication values: v='%s' s='%s'", databaseV.c_str(), databaseS.c_str()); + + // multiply with 2 since bytes are stored as hexstring + if (databaseV.size() != size_t(BufferSizes::SRP_6_V) * 2 || databaseS.size() != size_t(BufferSizes::SRP_6_S) * 2) + SetVSFields(rI); + else + { + s.SetHexStr(databaseS.c_str()); + v.SetHexStr(databaseV.c_str()); + } + + b.SetRand(19 * 8); + BigNumber gmod = g.ModExp(b, N); + B = ((v * 3) + gmod) % N; + + ASSERT(gmod.GetNumBytes() <= 32); + + BigNumber unk3; + unk3.SetRand(16 * 8); + + // Fill the response packet with the result + if (AuthHelper::IsAcceptedClientBuild(_build)) + pkt << uint8(WOW_SUCCESS); + else + pkt << uint8(WOW_FAIL_VERSION_INVALID); + + // B may be calculated < 32B so we force minimal length to 32B + pkt.append(B.AsByteArray(32).get(), 32); // 32 bytes + pkt << uint8(1); + pkt.append(g.AsByteArray(1).get(), 1); + pkt << uint8(32); + pkt.append(N.AsByteArray(32).get(), 32); + pkt.append(s.AsByteArray(int32(BufferSizes::SRP_6_S)).get(), size_t(BufferSizes::SRP_6_S)); // 32 bytes + pkt.append(unk3.AsByteArray(16).get(), 16); + uint8 securityFlags = 0; + + // Check if token is used + _tokenKey = fields[9].GetString(); + if (!_tokenKey.empty()) + securityFlags = 4; + + pkt << uint8(securityFlags); // security flags (0x0...0x04) + + if (securityFlags & 0x01) // PIN input + { + pkt << uint32(0); + pkt << uint64(0) << uint64(0); // 16 bytes hash? + } + + if (securityFlags & 0x02) // Matrix input + { + pkt << uint8(0); + pkt << uint8(0); + pkt << uint8(0); + pkt << uint8(0); + pkt << uint64(0); + } + + if (securityFlags & 0x04) // Security token input + pkt << uint8(1); + + TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] account %s is using '%s' locale (%u)", + ipAddress.c_str(), port, _accountInfo.Login.c_str(), _localizationName.c_str(), GetLocaleByName(_localizationName)); + SendPacket(pkt); - return true; } // Logon Proof command handler bool AuthSession::HandleLogonProof() { - TC_LOG_DEBUG("server.authserver", "Entering _HandleLogonProof"); + if (_sentProof) + return false; + + _sentProof = true; + // Read the packet sAuthLogonProof_C *logonProof = reinterpret_cast<sAuthLogonProof_C*>(GetReadBuffer().GetReadPointer()); @@ -487,7 +560,7 @@ bool AuthSession::HandleLogonProof() t3.SetBinary(hash, 20); sha.Initialize(); - sha.UpdateData(_login); + sha.UpdateData(_accountInfo.Login); sha.Finalize(); uint8 t4[SHA_DIGEST_LENGTH]; memcpy(t4, sha.GetDigest(), SHA_DIGEST_LENGTH); @@ -503,7 +576,7 @@ bool AuthSession::HandleLogonProof() // Check if SRP6 results match (password is correct), else send an error if (!memcmp(M.AsByteArray(sha.GetLength()).get(), logonProof->M1, 20)) { - TC_LOG_DEBUG("server.authserver", "'%s:%d' User '%s' successfully authenticated", GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _login.c_str()); + TC_LOG_DEBUG("server.authserver", "'%s:%d' User '%s' successfully authenticated", GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _accountInfo.Login.c_str()); // Update the sessionkey, last_ip, last login time and reset number of failed logins in the account table for this account // No SQL injection (escaped user name) and IP address as received by socket @@ -514,7 +587,7 @@ bool AuthSession::HandleLogonProof() stmt->setString(1, GetRemoteIpAddress().to_string().c_str()); stmt->setUInt32(2, GetLocaleByName(_localizationName)); stmt->setString(3, _os); - stmt->setString(4, _login); + stmt->setString(4, _accountInfo.Login); LoginDatabase.DirectExecute(stmt); OPENSSL_free((void*)K_hex); @@ -531,6 +604,7 @@ bool AuthSession::HandleLogonProof() std::string token(reinterpret_cast<char*>(GetReadBuffer().GetReadPointer() + sizeof(sAuthLogonProof_C) + sizeof(size)), size); GetReadBuffer().ReadCompleted(sizeof(size) + size); uint32 validToken = TOTP::GenerateToken(_tokenKey.c_str()); + _tokenKey.clear(); uint32 incomingToken = atoi(token.c_str()); if (validToken != incomingToken) { @@ -571,7 +645,7 @@ bool AuthSession::HandleLogonProof() } SendPacket(packet); - _isAuthenticated = true; + _status = STATUS_AUTHED; } else { @@ -583,7 +657,7 @@ bool AuthSession::HandleLogonProof() SendPacket(packet); TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] account %s tried to login with invalid password!", - GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _login.c_str()); + GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _accountInfo.Login.c_str()); uint32 MaxWrongPassCount = sConfigMgr->GetIntDefault("WrongPass.MaxCount", 0); @@ -591,7 +665,7 @@ bool AuthSession::HandleLogonProof() if (sConfigMgr->GetBoolDefault("WrongPass.Logging", false)) { PreparedStatement* logstmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_FALP_IP_LOGGING); - logstmt->setString(0, _login); + logstmt->setString(0, _accountInfo.Login); logstmt->setString(1, GetRemoteIpAddress().to_string()); logstmt->setString(2, "Logged on failed AccountLogin due wrong password"); @@ -602,42 +676,33 @@ bool AuthSession::HandleLogonProof() { //Increment number of failed logins by one and if it reaches the limit temporarily ban that account or IP PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_FAILEDLOGINS); - stmt->setString(0, _login); + stmt->setString(0, _accountInfo.Login); LoginDatabase.Execute(stmt); - stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_FAILEDLOGINS); - stmt->setString(0, _login); - - if (PreparedQueryResult loginfail = LoginDatabase.Query(stmt)) + if (_accountInfo.FailedLogins >= MaxWrongPassCount) { - uint32 failed_logins = (*loginfail)[1].GetUInt32(); + uint32 WrongPassBanTime = sConfigMgr->GetIntDefault("WrongPass.BanTime", 600); + bool WrongPassBanType = sConfigMgr->GetBoolDefault("WrongPass.BanType", false); + + if (WrongPassBanType) + { + stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT_AUTO_BANNED); + stmt->setUInt32(0, _accountInfo.Id); + stmt->setUInt32(1, WrongPassBanTime); + LoginDatabase.Execute(stmt); - if (failed_logins >= MaxWrongPassCount) + TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] account %s got banned for '%u' seconds because it failed to authenticate '%u' times", + GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _accountInfo.Login.c_str(), WrongPassBanTime, _accountInfo.FailedLogins); + } + else { - uint32 WrongPassBanTime = sConfigMgr->GetIntDefault("WrongPass.BanTime", 600); - bool WrongPassBanType = sConfigMgr->GetBoolDefault("WrongPass.BanType", false); - - if (WrongPassBanType) - { - uint32 acc_id = (*loginfail)[0].GetUInt32(); - stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT_AUTO_BANNED); - stmt->setUInt32(0, acc_id); - stmt->setUInt32(1, WrongPassBanTime); - LoginDatabase.Execute(stmt); - - TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] account %s got banned for '%u' seconds because it failed to authenticate '%u' times", - GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _login.c_str(), WrongPassBanTime, failed_logins); - } - else - { - stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_IP_AUTO_BANNED); - stmt->setString(0, GetRemoteIpAddress().to_string()); - stmt->setUInt32(1, WrongPassBanTime); - LoginDatabase.Execute(stmt); - - TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] IP got banned for '%u' seconds because account %s failed to authenticate '%u' times", - GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), WrongPassBanTime, _login.c_str(), failed_logins); - } + stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_IP_AUTO_BANNED); + stmt->setString(0, GetRemoteIpAddress().to_string()); + stmt->setUInt32(1, WrongPassBanTime); + LoginDatabase.Execute(stmt); + + TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] IP got banned for '%u' seconds because account %s failed to authenticate '%u' times", + GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), WrongPassBanTime, _accountInfo.Login.c_str(), _accountInfo.FailedLogins); } } } @@ -648,61 +713,88 @@ bool AuthSession::HandleLogonProof() bool AuthSession::HandleReconnectChallenge() { - TC_LOG_DEBUG("server.authserver", "Entering _HandleReconnectChallenge"); - sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(GetReadBuffer().GetReadPointer()); + if (_sentChallenge) + return false; - //TC_LOG_DEBUG("server.authserver", "[AuthChallenge] got full packet, %#04x bytes", challenge->size); - TC_LOG_DEBUG("server.authserver", "[AuthChallenge] name(%d): '%s'", challenge->I_len, challenge->I); + _sentChallenge = true; - _login.assign((const char*)challenge->I, challenge->I_len); + sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(GetReadBuffer().GetReadPointer()); + if (challenge->size - (sizeof(sAuthLogonChallenge_C) - AUTH_LOGON_CHALLENGE_INITIAL_SIZE - 1) != challenge->I_len) + return false; - PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_SESSIONKEY); - stmt->setString(0, _login); - PreparedQueryResult result = LoginDatabase.Query(stmt); + std::string login((const char*)challenge->I, challenge->I_len); + TC_LOG_DEBUG("server.authserver", "[ReconnectChallenge] '%s'", login.c_str()); - // Stop if the account is not found - if (!result) + if (_queryCallback) { - TC_LOG_ERROR("server.authserver", "'%s:%d' [ERROR] user %s tried to login and we cannot find his session key in the database.", - GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _login.c_str()); - return false; + ByteBuffer pkt; + pkt << uint8(AUTH_RECONNECT_CHALLENGE); + pkt << uint8(WOW_FAIL_DB_BUSY); + SendPacket(pkt); + + TC_LOG_DEBUG("server.authserver", "[ReconnectChallenge] %s attempted to log too quick after previous attempt!", login.c_str()); + return true; } - // Reinitialize build, expansion and the account securitylevel _build = challenge->build; _expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG)); - _os = (const char*)challenge->os; - - if (_os.size() > 4) - return false; + std::array<char, 5> os; + os.fill('\0'); + memcpy(os.data(), challenge->os, sizeof(challenge->os)); + _os = os.data(); // Restore string order as its byte order is reversed std::reverse(_os.begin(), _os.end()); - Field* fields = result->Fetch(); - uint8 secLevel = fields[2].GetUInt8(); - _accountSecurityLevel = secLevel <= SEC_ADMINISTRATOR ? AccountTypes(secLevel) : SEC_ADMINISTRATOR; + _localizationName.resize(4); + for (int i = 0; i < 4; ++i) + _localizationName[i] = challenge->country[4 - i - 1]; - K.SetHexStr((*result)[0].GetCString()); + // Get the account details from the account table + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_RECONNECTCHALLENGE); + stmt->setString(0, login); + + _queryCallback = std::bind(&AuthSession::ReconnectChallengeCallback, this, std::placeholders::_1); + _queryFuture = LoginDatabase.AsyncQuery(stmt); + return true; +} - // Sending response +void AuthSession::ReconnectChallengeCallback(PreparedQueryResult result) +{ ByteBuffer pkt; pkt << uint8(AUTH_RECONNECT_CHALLENGE); - pkt << uint8(0x00); + + if (!result) + { + pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT); + SendPacket(pkt); + return; + } + + Field* fields = result->Fetch(); + + _accountInfo.LoadResult(fields); + K.SetHexStr(fields[9].GetCString()); _reconnectProof.SetRand(16 * 8); - pkt.append(_reconnectProof.AsByteArray(16).get(), 16); // 16 bytes random + + pkt << uint8(WOW_SUCCESS); + pkt.append(_reconnectProof.AsByteArray(16).get(), 16); // 16 bytes random pkt << uint64(0x00) << uint64(0x00); // 16 bytes zeros SendPacket(pkt); - - return true; } + bool AuthSession::HandleReconnectProof() { TC_LOG_DEBUG("server.authserver", "Entering _HandleReconnectProof"); + if (_sentProof) + return false; + + _sentProof = true; + sAuthReconnectProof_C *reconnectProof = reinterpret_cast<sAuthReconnectProof_C*>(GetReadBuffer().GetReadPointer()); - if (_login.empty() || !_reconnectProof.GetNumBytes() || !K.GetNumBytes()) + if (_accountInfo.Login.empty() || !_reconnectProof.GetNumBytes() || !K.GetNumBytes()) return false; BigNumber t1; @@ -710,7 +802,7 @@ bool AuthSession::HandleReconnectProof() SHA1Hash sha; sha.Initialize(); - sha.UpdateData(_login); + sha.UpdateData(_accountInfo.Login); sha.UpdateBigNumbers(&t1, &_reconnectProof, &K, NULL); sha.Finalize(); @@ -722,13 +814,13 @@ bool AuthSession::HandleReconnectProof() pkt << uint8(0x00); pkt << uint16(0x00); // 2 bytes zeros SendPacket(pkt); - _isAuthenticated = true; + _status = STATUS_AUTHED; return true; } else { TC_LOG_ERROR("server.authserver", "'%s:%d' [ERROR] user %s tried to login, but session is invalid.", GetRemoteIpAddress().to_string().c_str(), - GetRemotePort(), _login.c_str()); + GetRemotePort(), _accountInfo.Login.c_str()); return false; } } @@ -772,20 +864,31 @@ bool AuthSession::HandleRealmList() { TC_LOG_DEBUG("server.authserver", "Entering _HandleRealmList"); - // Get the user id (else close the connection) - // No SQL injection (prepared statement) - PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_ID_BY_NAME); - stmt->setString(0, _login); - PreparedQueryResult result = LoginDatabase.Query(stmt); - if (!result) + if (_queryCallback) { - TC_LOG_ERROR("server.authserver", "'%s:%d' [ERROR] user %s tried to login but we cannot find him in the database.", GetRemoteIpAddress().to_string().c_str(), - GetRemotePort(), _login.c_str()); + TC_LOG_DEBUG("server.authserver", "[RealmList] %s attempted to get realmlist too quick after previous attempt!", _accountInfo.Login.c_str()); return false; } - Field* fields = result->Fetch(); - uint32 id = fields[0].GetUInt32(); + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_REALM_CHARACTER_COUNTS); + stmt->setUInt32(0, _accountInfo.Id); + + _queryCallback = std::bind(&AuthSession::RealmListCallback, this, std::placeholders::_1); + _queryFuture = LoginDatabase.AsyncQuery(stmt); + return true; +} + +void AuthSession::RealmListCallback(PreparedQueryResult result) +{ + std::map<uint32, uint8> characterCounts; + if (result) + { + do + { + Field* fields = result->Fetch(); + characterCounts[fields[0].GetUInt32()] = fields[1].GetUInt8(); + } while (result->NextRow()); + } // Update realm list if need sRealmList->UpdateIfNeed(); @@ -822,25 +925,17 @@ bool AuthSession::HandleRealmList() name = ss.str(); } - uint8 lock = (realm.allowedSecurityLevel > _accountSecurityLevel) ? 1 : 0; - - uint8 AmountOfCharacters = 0; - stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_NUM_CHARS_ON_REALM); - stmt->setUInt32(0, realm.m_ID); - stmt->setUInt32(1, id); - result = LoginDatabase.Query(stmt); - if (result) - AmountOfCharacters = (*result)[0].GetUInt8(); + uint8 lock = (realm.allowedSecurityLevel > _accountInfo.SecurityLevel) ? 1 : 0; - pkt << realm.icon; // realm type + pkt << uint8(realm.icon); // realm type if (_expversion & POST_BC_EXP_FLAG) // only 2.x and 3.x clients - pkt << lock; // if 1, then realm locked + pkt << uint8(lock); // if 1, then realm locked pkt << uint8(flag); // RealmFlags pkt << name; pkt << boost::lexical_cast<std::string>(GetAddressForClient(realm, GetRemoteIpAddress())); - pkt << realm.populationLevel; - pkt << AmountOfCharacters; - pkt << realm.timezone; // realm category + pkt << float(realm.populationLevel); + pkt << uint8(characterCounts[realm.m_ID]); + pkt << uint8(realm.timezone); // realm category if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients pkt << uint8(realm.m_ID); else @@ -882,32 +977,6 @@ bool AuthSession::HandleRealmList() hdr.append(RealmListSizeBuffer); // append RealmList's size buffer hdr.append(pkt); // append realms in the realmlist SendPacket(hdr); - return true; -} - -// Resume patch transfer -bool AuthSession::HandleXferResume() -{ - TC_LOG_DEBUG("server.authserver", "Entering _HandleXferResume"); - //uint8 - //uint64 - return true; -} - -// Cancel patch transfer -bool AuthSession::HandleXferCancel() -{ - TC_LOG_DEBUG("server.authserver", "Entering _HandleXferCancel"); - //uint8 - return false; -} - -// Accept patch transfer -bool AuthSession::HandleXferAccept() -{ - TC_LOG_DEBUG("server.authserver", "Entering _HandleXferAccept"); - //uint8 - return true; } // Make the SRP6 calculation from hash in dB @@ -940,7 +1009,7 @@ void AuthSession::SetVSFields(const std::string& rI) PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_VS); stmt->setString(0, v_hex); stmt->setString(1, s_hex); - stmt->setString(2, _login); + stmt->setString(2, _accountInfo.Login); LoginDatabase.Execute(stmt); OPENSSL_free(v_hex); diff --git a/src/server/authserver/Server/AuthSession.h b/src/server/authserver/Server/AuthSession.h index d40e0852b57..1babb7407a9 100644 --- a/src/server/authserver/Server/AuthSession.h +++ b/src/server/authserver/Server/AuthSession.h @@ -23,29 +23,48 @@ #include "ByteBuffer.h" #include "Socket.h" #include "BigNumber.h" +#include "Callback.h" #include <memory> #include <boost/asio/ip/tcp.hpp> using boost::asio::ip::tcp; +class Field; struct AuthHandler; +enum AuthStatus +{ + STATUS_CONNECTED = 0, + STATUS_AUTHED +}; + +struct AccountInfo +{ + void LoadResult(Field* fields); + + uint32 Id = 0; + std::string Login; + bool IsLockedToIP = false; + std::string LockCountry; + std::string LastIP; + uint32 FailedLogins = 0; + bool IsBanned = false; + bool IsPermanenetlyBanned = false; + AccountTypes SecurityLevel = SEC_PLAYER; + std::string TokenKey; +}; + class AuthSession : public Socket<AuthSession> { + typedef Socket<AuthSession> AuthSocket; + public: static std::unordered_map<uint8, AuthHandler> InitHandlers(); - AuthSession(tcp::socket&& socket) : Socket(std::move(socket)), - _isAuthenticated(false), _build(0), _expversion(0), _accountSecurityLevel(SEC_PLAYER) - { - N.SetHexStr("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7"); - g.SetDword(7); - } + AuthSession(tcp::socket&& socket); - void Start() override - { - AsyncRead(); - } + void Start() override; + bool Update() override; void SendPacket(ByteBuffer& packet); @@ -59,10 +78,10 @@ private: bool HandleReconnectProof(); bool HandleRealmList(); - //data transfer handle for patch - bool HandleXferResume(); - bool HandleXferCancel(); - bool HandleXferAccept(); + void CheckIpCallback(PreparedQueryResult result); + void LogonChallengeCallback(PreparedQueryResult result); + void ReconnectChallengeCallback(PreparedQueryResult result); + void RealmListCallback(PreparedQueryResult result); void SetVSFields(const std::string& rI); @@ -71,22 +90,27 @@ private: BigNumber K; BigNumber _reconnectProof; - bool _isAuthenticated; + bool _sentChallenge; + bool _sentProof; + + AuthStatus _status; + AccountInfo _accountInfo; std::string _tokenKey; - std::string _login; std::string _localizationName; std::string _os; + std::string _ipCountry; uint16 _build; uint8 _expversion; - AccountTypes _accountSecurityLevel; + PreparedQueryResultFuture _queryFuture; + std::function<void(PreparedQueryResult)> _queryCallback; }; #pragma pack(push, 1) struct AuthHandler { - uint32 status; + AuthStatus status; size_t packetSize; bool (AuthSession::*handler)(); }; diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 140f3bf31c9..ab68aca2a8c 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -72,7 +72,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() "FROM characters WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_GROUP_MEMBER, "SELECT guid FROM group_member WHERE memberGuid = ?", CONNECTION_BOTH); - PrepareStatement(CHAR_SEL_CHARACTER_INSTANCE, "SELECT id, permanent, map, difficulty, resettime FROM character_instance LEFT JOIN instance ON instance = id WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_CHARACTER_INSTANCE, "SELECT id, permanent, map, difficulty, extendState, resettime FROM character_instance LEFT JOIN instance ON instance = id WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_AURAS, "SELECT casterGuid, spell, effectMask, recalculateMask, stackCount, amount0, amount1, amount2, " "base_amount0, base_amount1, base_amount2, maxDuration, remainTime, remainCharges FROM character_aura WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_SPELL, "SELECT spell, active, disabled FROM character_spell WHERE guid = ?", CONNECTION_ASYNC); @@ -405,8 +405,8 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_UPD_WORLDSTATE, "UPDATE worldstates SET value = ? WHERE entry = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_WORLDSTATE, "INSERT INTO worldstates (entry, value) VALUES (?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID, "DELETE FROM character_instance WHERE guid = ? AND instance = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_UPD_CHAR_INSTANCE, "UPDATE character_instance SET instance = ?, permanent = ? WHERE guid = ? AND instance = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_INS_CHAR_INSTANCE, "INSERT INTO character_instance (guid, instance, permanent) VALUES (?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_CHAR_INSTANCE, "UPDATE character_instance SET instance = ?, permanent = ?, extendState = ? WHERE guid = ? AND instance = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_CHAR_INSTANCE, "INSERT INTO character_instance (guid, instance, permanent, extendState) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_GENDER_PLAYERBYTES, "UPDATE characters SET gender = ?, playerBytes = ?, playerBytes2 = ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHARACTER_SKILL, "DELETE FROM character_skills WHERE guid = ? AND skill = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_ADD_CHARACTER_SOCIAL_FLAGS, "UPDATE character_social SET flags = flags | ? WHERE guid = ? AND friend = ?", CONNECTION_ASYNC); @@ -434,6 +434,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_SEL_CHAR_CLASS_LVL_AT_LOGIN, "SELECT class, level, at_login, knownTitles FROM characters WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_CHAR_AT_LOGIN_TITLES, "SELECT at_login, knownTitles FROM characters WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_INSTANCE, "SELECT data, completedEncounters FROM instance WHERE map = ? AND id = ?", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_PERM_BIND_BY_INSTANCE, "SELECT guid FROM character_instance WHERE instance = ? and permanent = 1", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_CHAR_COD_ITEM_MAIL, "SELECT id, messageType, mailTemplateId, sender, subject, body, money, has_items FROM mail WHERE receiver = ? AND has_items <> 0 AND cod <> 0", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_CHAR_SOCIAL, "SELECT DISTINCT guid FROM character_social WHERE friend = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_CHAR_OLD_CHARS, "SELECT guid, deleteInfos_Account FROM characters WHERE deleteDate IS NOT NULL AND deleteDate < ?", CONNECTION_SYNCH); @@ -470,9 +471,10 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_INS_CHAR_GIFT, "INSERT INTO character_gifts (guid, item_guid, entry, flags) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_INSTANCE_BY_INSTANCE, "DELETE FROM instance WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE, "DELETE FROM character_instance WHERE instance = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_DEL_CHAR_INSTANCE_BY_MAP_DIFF, "DELETE FROM character_instance USING character_instance LEFT JOIN instance ON character_instance.instance = id WHERE map = ? and difficulty = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_EXPIRED_CHAR_INSTANCE_BY_MAP_DIFF, "DELETE FROM character_instance USING character_instance LEFT JOIN instance ON character_instance.instance = id WHERE (extendState = 0 or permanent = 0) and map = ? and difficulty = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_GROUP_INSTANCE_BY_MAP_DIFF, "DELETE FROM group_instance USING group_instance LEFT JOIN instance ON group_instance.instance = id WHERE map = ? and difficulty = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_DEL_INSTANCE_BY_MAP_DIFF, "DELETE FROM instance WHERE map = ? and difficulty = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_EXPIRED_INSTANCE_BY_MAP_DIFF, "DELETE FROM instance WHERE map = ? and difficulty = ? and (SELECT guid FROM character_instance WHERE extendState != 0 AND instance = id LIMIT 1) IS NULL", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_EXPIRE_CHAR_INSTANCE_BY_MAP_DIFF, "UPDATE character_instance LEFT JOIN instance ON character_instance.instance = id SET extendState = extendState-1 WHERE map = ? and difficulty = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_MAIL_ITEM_BY_ID, "DELETE FROM mail_items WHERE mail_id = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_PETITION, "INSERT INTO petition (ownerguid, petitionguid, name, type) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_PETITION_BY_GUID, "DELETE FROM petition WHERE petitionguid = ?", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index c0ff9cbbfbe..19d4a609e77 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -365,6 +365,7 @@ enum CharacterDatabaseStatements CHAR_SEL_CHAR_CLASS_LVL_AT_LOGIN, CHAR_SEL_CHAR_AT_LOGIN_TITLES, CHAR_SEL_INSTANCE, + CHAR_SEL_PERM_BIND_BY_INSTANCE, CHAR_SEL_CHAR_COD_ITEM_MAIL, CHAR_SEL_CHAR_SOCIAL, CHAR_SEL_CHAR_OLD_CHARS, @@ -396,9 +397,10 @@ enum CharacterDatabaseStatements CHAR_INS_CHAR_GIFT, CHAR_DEL_INSTANCE_BY_INSTANCE, CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE, - CHAR_DEL_CHAR_INSTANCE_BY_MAP_DIFF, + CHAR_DEL_EXPIRED_CHAR_INSTANCE_BY_MAP_DIFF, CHAR_DEL_GROUP_INSTANCE_BY_MAP_DIFF, - CHAR_DEL_INSTANCE_BY_MAP_DIFF, + CHAR_DEL_EXPIRED_INSTANCE_BY_MAP_DIFF, + CHAR_UPD_EXPIRE_CHAR_INSTANCE_BY_MAP_DIFF, CHAR_DEL_MAIL_ITEM_BY_ID, CHAR_INS_PETITION, CHAR_DEL_PETITION_BY_GUID, diff --git a/src/server/database/Database/Implementation/LoginDatabase.cpp b/src/server/database/Database/Implementation/LoginDatabase.cpp index 66847f0a6a0..163a2bebeb9 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.cpp +++ b/src/server/database/Database/Implementation/LoginDatabase.cpp @@ -28,29 +28,30 @@ void LoginDatabaseConnection::DoPrepareStatements() PrepareStatement(LOGIN_SEL_IP_INFO, "(SELECT unbandate > UNIX_TIMESTAMP() OR unbandate = bandate AS banned, NULL as country FROM ip_banned WHERE ip = ?) " "UNION " "(SELECT NULL AS banned, country FROM ip2nation WHERE INET_NTOA(ip) = ?)", CONNECTION_ASYNC); - PrepareStatement(LOGIN_SEL_IP_BANNED, "SELECT * FROM ip_banned WHERE ip = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_INS_IP_AUTO_BANNED, "INSERT INTO ip_banned (ip, bandate, unbandate, bannedby, banreason) VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, 'Trinity Auth', 'Failed login autoban')", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_IP_BANNED_ALL, "SELECT ip, bandate, unbandate, bannedby, banreason FROM ip_banned WHERE (bandate = unbandate OR unbandate > UNIX_TIMESTAMP()) ORDER BY unbandate", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_IP_BANNED_BY_IP, "SELECT ip, bandate, unbandate, bannedby, banreason FROM ip_banned WHERE (bandate = unbandate OR unbandate > UNIX_TIMESTAMP()) AND ip LIKE CONCAT('%%', ?, '%%') ORDER BY unbandate", CONNECTION_SYNCH); - PrepareStatement(LOGIN_SEL_ACCOUNT_BANNED, "SELECT bandate, unbandate FROM account_banned WHERE id = ? AND active = 1", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_ACCOUNT_BANNED_ALL, "SELECT account.id, username FROM account, account_banned WHERE account.id = account_banned.id AND active = 1 GROUP BY account.id", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_ACCOUNT_BANNED_BY_USERNAME, "SELECT account.id, username FROM account, account_banned WHERE account.id = account_banned.id AND active = 1 AND username LIKE CONCAT('%%', ?, '%%') GROUP BY account.id", CONNECTION_SYNCH); PrepareStatement(LOGIN_INS_ACCOUNT_AUTO_BANNED, "INSERT INTO account_banned VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, 'Trinity Auth', 'Failed login autoban', 1)", CONNECTION_ASYNC); PrepareStatement(LOGIN_DEL_ACCOUNT_BANNED, "DELETE FROM account_banned WHERE id = ?", CONNECTION_ASYNC); - PrepareStatement(LOGIN_SEL_SESSIONKEY, "SELECT a.sessionkey, a.id, aa.gmlevel FROM account a LEFT JOIN account_access aa ON (a.id = aa.id) WHERE username = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_UPD_VS, "UPDATE account SET v = ?, s = ? WHERE username = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_LOGONPROOF, "UPDATE account SET sessionkey = ?, last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE username = ?", CONNECTION_SYNCH); - PrepareStatement(LOGIN_SEL_LOGONCHALLENGE, "SELECT a.sha_pass_hash, a.id, a.locked, a.lock_country, a.last_ip, aa.gmlevel, a.v, a.s, a.token_key FROM account a LEFT JOIN account_access aa ON (a.id = aa.id) WHERE a.username = ?", CONNECTION_SYNCH); + PrepareStatement(LOGIN_SEL_LOGONCHALLENGE, "SELECT a.id, UPPER(a.username), a.locked, a.lock_country, a.last_ip, a.failed_logins, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, " + "ab.unbandate = ab.bandate, aa.gmlevel, a.token_key, a.sha_pass_hash, a.v, a.s " + "FROM account a LEFT JOIN account_access aa ON a.id = aa.id LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 WHERE a.username = ?", CONNECTION_ASYNC); + PrepareStatement(LOGIN_SEL_RECONNECTCHALLENGE, "SELECT a.id, UPPER(a.username), a.locked, a.lock_country, a.last_ip, a.failed_logins, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, " + "ab.unbandate = ab.bandate, aa.gmlevel, a.sessionKey " + "FROM account a LEFT JOIN account_access aa ON a.id = aa.id LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 WHERE a.username = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_LOGON_COUNTRY, "SELECT country FROM ip2nation WHERE ip < ? ORDER BY ip DESC LIMIT 0,1", CONNECTION_SYNCH); PrepareStatement(LOGIN_UPD_FAILEDLOGINS, "UPDATE account SET failed_logins = failed_logins + 1 WHERE username = ?", CONNECTION_ASYNC); - PrepareStatement(LOGIN_SEL_FAILEDLOGINS, "SELECT id, failed_logins FROM account WHERE username = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_ACCOUNT_ID_BY_NAME, "SELECT id FROM account WHERE username = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_ACCOUNT_LIST_BY_NAME, "SELECT id, username FROM account WHERE username = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_BY_NAME, "SELECT a.id, a.sessionkey, a.last_ip, a.locked, a.lock_country, a.expansion, a.mutetime, a.locale, a.recruiter, a.os, aa.gmLevel, " "ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id FROM account a LEFT JOIN account_access aa ON a.id = aa.id AND aa.RealmID IN (-1, ?) " "LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 LEFT JOIN account r ON a.id = r.recruiter WHERE a.username = ? ORDER BY aa.RealmID DESC LIMIT 1", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_ACCOUNT_LIST_BY_EMAIL, "SELECT id, username FROM account WHERE email = ?", CONNECTION_SYNCH); - PrepareStatement(LOGIN_SEL_NUM_CHARS_ON_REALM, "SELECT numchars FROM realmcharacters WHERE realmid = ? AND acctid= ?", CONNECTION_SYNCH); + PrepareStatement(LOGIN_SEL_REALM_CHARACTER_COUNTS, "SELECT realmid, numchars FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_ACCOUNT_BY_IP, "SELECT id, username FROM account WHERE last_ip = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_ACCOUNT_BY_ID, "SELECT 1 FROM account WHERE id = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_INS_IP_BANNED, "INSERT INTO ip_banned (ip, bandate, unbandate, bannedby, banreason) VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, ?, ?)", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/LoginDatabase.h b/src/server/database/Database/Implementation/LoginDatabase.h index 69c2e758551..a3789fa2557 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.h +++ b/src/server/database/Database/Implementation/LoginDatabase.h @@ -33,25 +33,22 @@ enum LoginDatabaseStatements LOGIN_DEL_EXPIRED_IP_BANS, LOGIN_UPD_EXPIRED_ACCOUNT_BANS, LOGIN_SEL_IP_INFO, - LOGIN_SEL_IP_BANNED, LOGIN_INS_IP_AUTO_BANNED, - LOGIN_SEL_ACCOUNT_BANNED, LOGIN_SEL_ACCOUNT_BANNED_ALL, LOGIN_SEL_ACCOUNT_BANNED_BY_USERNAME, LOGIN_INS_ACCOUNT_AUTO_BANNED, LOGIN_DEL_ACCOUNT_BANNED, - LOGIN_SEL_SESSIONKEY, LOGIN_UPD_VS, LOGIN_UPD_LOGONPROOF, LOGIN_SEL_LOGONCHALLENGE, + LOGIN_SEL_RECONNECTCHALLENGE, LOGIN_SEL_LOGON_COUNTRY, LOGIN_UPD_FAILEDLOGINS, - LOGIN_SEL_FAILEDLOGINS, LOGIN_SEL_ACCOUNT_ID_BY_NAME, LOGIN_SEL_ACCOUNT_LIST_BY_NAME, LOGIN_SEL_ACCOUNT_INFO_BY_NAME, LOGIN_SEL_ACCOUNT_LIST_BY_EMAIL, - LOGIN_SEL_NUM_CHARS_ON_REALM, + LOGIN_SEL_REALM_CHARACTER_COUNTS, LOGIN_SEL_ACCOUNT_BY_IP, LOGIN_INS_IP_BANNED, LOGIN_DEL_IP_NOT_BANNED, diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index c254a9124c1..352ae635878 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -133,7 +133,7 @@ void CreatureAI::MoveInLineOfSight(Unit* who) if (me->GetCreatureType() == CREATURE_TYPE_NON_COMBAT_PET) // non-combat pets should just stand there and look good;) return; - if (me->CanStartAttack(who, false)) + if (me->HasReactState(REACT_AGGRESSIVE) && me->CanStartAttack(who, false)) AttackStart(who); //else if (who->GetVictim() && me->IsFriendlyTo(who) // && me->IsWithinDistInMap(who, sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_RADIUS)) @@ -350,7 +350,7 @@ int32 CreatureAI::VisualizeBoundary(uint32 duration, Unit* owner, bool fill) con return boundsWarning ? LANG_CREATURE_MOVEMENT_MAYBE_UNBOUNDED : 0; } -bool CreatureAI::CheckBoundary(Position* who) const +bool CreatureAI::CheckBoundary(Position const* who) const { if (!who) who = me; diff --git a/src/server/game/AI/CreatureAI.h b/src/server/game/AI/CreatureAI.h index f250a79ea25..239fda577a7 100644 --- a/src/server/game/AI/CreatureAI.h +++ b/src/server/game/AI/CreatureAI.h @@ -79,7 +79,7 @@ class CreatureAI : public UnitAI Creature* DoSummon(uint32 entry, WorldObject* obj, float radius = 5.0f, uint32 despawnTime = 30000, TempSummonType summonType = TEMPSUMMON_CORPSE_TIMED_DESPAWN); Creature* DoSummonFlyer(uint32 entry, WorldObject* obj, float flightZ, float radius = 5.0f, uint32 despawnTime = 30000, TempSummonType summonType = TEMPSUMMON_CORPSE_TIMED_DESPAWN); - bool CheckBoundary(Position* who = nullptr) const; + bool CheckBoundary(Position const* who = nullptr) const; void SetBoundary(CreatureBoundary const* boundary) { _boundary = boundary; me->DoImmediateBoundaryCheck(); } public: enum EvadeReason diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.h b/src/server/game/AI/ScriptedAI/ScriptedCreature.h index 1f0bbab72c4..5452a033a17 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.h +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.h @@ -360,6 +360,8 @@ class BossAI : public ScriptedAI void JustDied(Unit* /*killer*/) override { _JustDied(); } void JustReachedHome() override { _JustReachedHome(); } + bool CanAIAttack(Unit const* target) const override { return CheckBoundary(target); } + protected: void _Reset(); void _EnterCombat(); diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp index 808491495a9..c08d1508774 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.cpp +++ b/src/server/game/AI/SmartScripts/SmartScript.cpp @@ -1998,7 +1998,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (Creature* creature = (*itr)->ToCreature()) { creature->GetMotionMaster()->Clear(); - creature->GetMotionMaster()->MoveJump(e.target.x, e.target.y, e.target.z, (float)e.action.jump.speedxy, (float)e.action.jump.speedz); + creature->GetMotionMaster()->MoveJump(e.target.x, e.target.y, e.target.z, 0.0f, (float)e.action.jump.speedxy, (float)e.action.jump.speedz); // @todo add optional jump orientation support? } } /// @todo Resume path when reached jump location diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index b030f42bef7..1acfeacbf83 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -1399,7 +1399,10 @@ void GameObject::Use(Unit* user) player->SendCinematicStart(info->camera.cinematicId); if (info->camera.eventID) + { GetMap()->ScriptsStart(sEventScripts, info->camera.eventID, player, this); + EventInform(info->camera.eventID, user); + } return; } diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 9ae9f21c967..c7f674ec72c 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -18375,8 +18375,9 @@ void Player::_LoadBoundInstances(PreparedQueryResult result) uint32 mapId = fields[2].GetUInt16(); uint32 instanceId = fields[0].GetUInt32(); uint8 difficulty = fields[3].GetUInt8(); + BindExtensionState extendState = BindExtensionState(fields[4].GetUInt8()); - time_t resetTime = time_t(fields[4].GetUInt32()); + time_t resetTime = time_t(fields[5].GetUInt32()); // the resettime for normal instances is only saved when the InstanceSave is unloaded // so the value read from the DB may be wrong here but only if the InstanceSave is loaded // and in that case it is not used @@ -18425,13 +18426,13 @@ void Player::_LoadBoundInstances(PreparedQueryResult result) // since non permanent binds are always solo bind, they can always be reset if (InstanceSave* save = sInstanceSaveMgr->AddInstanceSave(mapId, instanceId, Difficulty(difficulty), resetTime, !perm, true)) - BindToInstance(save, perm, true); + BindToInstance(save, perm, extendState, true); } while (result->NextRow()); } } -InstancePlayerBind* Player::GetBoundInstance(uint32 mapid, Difficulty difficulty) +InstancePlayerBind* Player::GetBoundInstance(uint32 mapid, Difficulty difficulty, bool withExpired) { // some instances only have one difficulty MapDifficulty const* mapDiff = GetDownscaledMapDifficultyData(mapid, difficulty); @@ -18440,9 +18441,9 @@ InstancePlayerBind* Player::GetBoundInstance(uint32 mapid, Difficulty difficulty BoundInstancesMap::iterator itr = m_boundInstances[difficulty].find(mapid); if (itr != m_boundInstances[difficulty].end()) - return &itr->second; - else - return NULL; + if (itr->second.extendState || withExpired) + return &itr->second; + return nullptr; } InstanceSave* Player::GetInstanceSave(uint32 mapid, bool raid) @@ -18485,24 +18486,32 @@ void Player::UnbindInstance(BoundInstancesMap::iterator &itr, Difficulty difficu } } -InstancePlayerBind* Player::BindToInstance(InstanceSave* save, bool permanent, bool load) +InstancePlayerBind* Player::BindToInstance(InstanceSave* save, bool permanent, BindExtensionState extendState, bool load) { if (save) { InstancePlayerBind& bind = m_boundInstances[save->GetDifficulty()][save->GetMapId()]; + if (extendState == EXTEND_STATE_KEEP) // special flag, keep the player's current extend state when updating for new boss down + { + if (save == bind.save) + extendState = bind.extendState; + else + extendState = EXTEND_STATE_NORMAL; + } if (!load) { if (bind.save) { // update the save when the group kills a boss - if (permanent != bind.perm || save != bind.save) + if (permanent != bind.perm || save != bind.save || extendState != bind.extendState) { PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_INSTANCE); stmt->setUInt32(0, save->GetInstanceId()); stmt->setBool(1, permanent); - stmt->setUInt32(2, GetGUID().GetCounter()); - stmt->setUInt32(3, bind.save->GetInstanceId()); + stmt->setUInt8(2, extendState); + stmt->setUInt32(3, GetGUID().GetCounter()); + stmt->setUInt32(4, bind.save->GetInstanceId()); CharacterDatabase.Execute(stmt); } @@ -18514,6 +18523,7 @@ InstancePlayerBind* Player::BindToInstance(InstanceSave* save, bool permanent, b stmt->setUInt32(0, GetGUID().GetCounter()); stmt->setUInt32(1, save->GetInstanceId()); stmt->setBool(2, permanent); + stmt->setUInt8(3, extendState); CharacterDatabase.Execute(stmt); } @@ -18531,9 +18541,10 @@ InstancePlayerBind* Player::BindToInstance(InstanceSave* save, bool permanent, b bind.save = save; bind.perm = permanent; + bind.extendState = extendState; if (!load) TC_LOG_DEBUG("maps", "Player::BindToInstance: %s(%d) is now bound to map %d, instance %d, difficulty %d", GetName().c_str(), GetGUID().GetCounter(), save->GetMapId(), save->GetInstanceId(), save->GetDifficulty()); - sScriptMgr->OnPlayerBindToInstance(this, save->GetDifficulty(), save->GetMapId(), permanent); + sScriptMgr->OnPlayerBindToInstance(this, save->GetDifficulty(), save->GetMapId(), permanent, uint8(extendState)); return &bind; } @@ -18551,7 +18562,7 @@ void Player::BindToInstance() GetSession()->SendPacket(&data); if (!IsGameMaster()) { - BindToInstance(mapSave, true); + BindToInstance(mapSave, true, EXTEND_STATE_KEEP); GetSession()->SendCalendarRaidLockout(mapSave, true); } } @@ -18577,15 +18588,19 @@ void Player::SendRaidInfo() { for (BoundInstancesMap::iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end(); ++itr) { - if (itr->second.perm) - { - InstanceSave* save = itr->second.save; - data << uint32(save->GetMapId()); // map id - data << uint32(save->GetDifficulty()); // difficulty - data << uint64(save->GetInstanceId()); // instance id - data << uint8(1); // expired = 0 - data << uint8(0); // extended = 1 - data << uint32(save->GetResetTime() - now); // reset time + InstancePlayerBind const& bind = itr->second; + if (bind.perm) + { + InstanceSave* save = bind.save; + data << uint32(save->GetMapId()); // map id + data << uint32(save->GetDifficulty()); // difficulty + data << uint64(save->GetInstanceId()); // instance id + data << uint8(bind.extendState != EXTEND_STATE_EXPIRED); // expired = 0 + data << uint8(bind.extendState == EXTEND_STATE_EXTENDED); // extended = 1 + time_t nextReset = save->GetResetTime(); + if (bind.extendState == EXTEND_STATE_EXTENDED) + nextReset = sInstanceSaveMgr->GetSubsequentResetTime(save->GetMapId(), save->GetDifficulty(), save->GetResetTime()); + data << uint32(nextReset - now); // reset time ++counter; } } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 9b64023698c..2388cf9d0c7 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -834,14 +834,27 @@ enum PlayerDelayedOperations // Maximum money amount : 2^31 - 1 extern uint32 const MAX_MONEY_AMOUNT; +enum BindExtensionState +{ + EXTEND_STATE_EXPIRED = 0, + EXTEND_STATE_NORMAL = 1, + EXTEND_STATE_EXTENDED = 2, + EXTEND_STATE_KEEP = 255 // special state: keep current save type +}; struct InstancePlayerBind { InstanceSave* save; - bool perm; /* permanent PlayerInstanceBinds are created in Raid/Heroic instances for players - that aren't already permanently bound when they are inside when a boss is killed - or when they enter an instance that the group leader is permanently bound to. */ - InstancePlayerBind() : save(NULL), perm(false) { } + that aren't already permanently bound when they are inside when a boss is killed + or when they enter an instance that the group leader is permanently bound to. */ + bool perm; + /* extend state listing: + EXPIRED - doesn't affect anything unless manually re-extended by player + NORMAL - standard state + EXTENDED - won't be promoted to EXPIRED at next reset period, will instead be promoted to NORMAL */ + BindExtensionState extendState; + + InstancePlayerBind() : save(NULL), perm(false), extendState(EXTEND_STATE_NORMAL) { } }; struct AccessRequirement @@ -2115,12 +2128,12 @@ class Player : public Unit, public GridObject<Player> bool m_InstanceValid; // permanent binds and solo binds by difficulty BoundInstancesMap m_boundInstances[MAX_DIFFICULTY]; - InstancePlayerBind* GetBoundInstance(uint32 mapid, Difficulty difficulty); + InstancePlayerBind* GetBoundInstance(uint32 mapid, Difficulty difficulty, bool withExpired = false); BoundInstancesMap& GetBoundInstances(Difficulty difficulty) { return m_boundInstances[difficulty]; } InstanceSave* GetInstanceSave(uint32 mapid, bool raid); void UnbindInstance(uint32 mapid, Difficulty difficulty, bool unload = false); void UnbindInstance(BoundInstancesMap::iterator &itr, Difficulty difficulty, bool unload = false); - InstancePlayerBind* BindToInstance(InstanceSave* save, bool permanent, bool load = false); + InstancePlayerBind* BindToInstance(InstanceSave* save, bool permanent, BindExtensionState extendState = EXTEND_STATE_NORMAL, bool load = false); void BindToInstance(); void SetPendingBind(uint32 instanceId, uint32 bindTimer); bool HasPendingBind() const { return _pendingBindId > 0; } diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index bdc97e22649..c21301c27b2 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -4355,13 +4355,10 @@ void Unit::GetDispellableAuraList(Unit* caster, uint32 dispelMask, DispelCharges if (aura->GetSpellInfo()->GetDispelMask() & dispelMask) { - if (aura->GetSpellInfo()->Dispel == DISPEL_MAGIC) - { - // do not remove positive auras if friendly target - // negative auras if non-friendly target - if (aurApp->IsPositive() == IsFriendlyTo(caster)) - continue; - } + // do not remove positive auras if friendly target + // negative auras if non-friendly target + if (aurApp->IsPositive() == IsFriendlyTo(caster)) + continue; // The charges / stack amounts don't count towards the total number of auras that can be dispelled. // Ie: A dispel on a target with 5 stacks of Winters Chill and a Polymorph has 1 / (1 + 1) -> 50% chance to dispell @@ -8328,8 +8325,6 @@ bool Unit::HandleProcTriggerSpell(Unit* victim, uint32 damage, AuraEffect* trigg case 52914: case 52915: case 52910: - // Honor Among Thieves - case 52916: { target = triggeredByAura->GetBase()->GetCaster(); if (!target) @@ -16861,12 +16856,12 @@ void Unit::JumpTo(float speedXY, float speedZ, bool forward) } } -void Unit::JumpTo(WorldObject* obj, float speedZ) +void Unit::JumpTo(WorldObject* obj, float speedZ, bool withOrientation) { float x, y, z; obj->GetContactPoint(this, x, y, z); float speedXY = GetExactDist2d(x, y) * 10.0f / speedZ; - GetMotionMaster()->MoveJump(x, y, z, speedXY, speedZ); + GetMotionMaster()->MoveJump(x, y, z, GetAngle(obj), speedXY, speedZ, EVENT_JUMP, withOrientation); } bool Unit::HandleSpellClick(Unit* clicker, int8 seatId) diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 8572c791d67..4565160dc93 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1567,7 +1567,7 @@ class Unit : public WorldObject void KnockbackFrom(float x, float y, float speedXY, float speedZ); void JumpTo(float speedXY, float speedZ, bool forward = true); - void JumpTo(WorldObject* obj, float speedZ); + void JumpTo(WorldObject* obj, float speedZ, bool withOrientation = false); void MonsterMoveWithSpeed(float x, float y, float z, float speed, bool generatePath = false, bool forceDestination = false); //void SetFacing(float ori, WorldObject* obj = NULL); diff --git a/src/server/game/Grids/Notifiers/GridNotifiers.cpp b/src/server/game/Grids/Notifiers/GridNotifiers.cpp index c48d1947eec..2cdbdca4e4f 100644 --- a/src/server/game/Grids/Notifiers/GridNotifiers.cpp +++ b/src/server/game/Grids/Notifiers/GridNotifiers.cpp @@ -131,7 +131,7 @@ inline void CreatureUnitRelocationWorker(Creature* c, Unit* u) if (!u->IsAlive() || !c->IsAlive() || c == u || u->IsInFlight()) return; - if (c->HasReactState(REACT_AGGRESSIVE) && !c->HasUnitState(UNIT_STATE_SIGHTLESS)) + if (!c->HasUnitState(UNIT_STATE_SIGHTLESS)) { if (c->IsAIEnabled && c->CanSeeOrDetect(u, false, true)) c->AI()->MoveInLineOfSight_Safe(u); diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp index 43159828a3c..99c5d610e64 100644 --- a/src/server/game/Groups/Group.cpp +++ b/src/server/game/Groups/Group.cpp @@ -687,7 +687,8 @@ void Group::ConvertLeaderInstancesToGroup(Player* player, Group* group, bool swi for (Player::BoundInstancesMap::iterator itr = player->m_boundInstances[i].begin(); itr != player->m_boundInstances[i].end();) { if (!switchLeader || !group->GetBoundInstance(itr->second.save->GetDifficulty(), itr->first)) - group->BindToInstance(itr->second.save, itr->second.perm, false); + if (itr->second.extendState) // not expired + group->BindToInstance(itr->second.save, itr->second.perm, false); // permanent binds are not removed if (switchLeader && !itr->second.perm) diff --git a/src/server/game/Handlers/CalendarHandler.cpp b/src/server/game/Handlers/CalendarHandler.cpp index 8bd7086fc1b..540eeba0752 100644 --- a/src/server/game/Handlers/CalendarHandler.cpp +++ b/src/server/game/Handlers/CalendarHandler.cpp @@ -695,6 +695,21 @@ void WorldSession::HandleSetSavedInstanceExtend(WorldPacket& recvData) recvData >> mapId >> difficulty>> toggleExtend; TC_LOG_DEBUG("network", "CMSG_SET_SAVED_INSTANCE_EXTEND - MapId: %u, Difficulty: %u, ToggleExtend: %s", mapId, difficulty, toggleExtend ? "On" : "Off"); + if (Player* player = GetPlayer()) + { + InstancePlayerBind* instanceBind = player->GetBoundInstance(mapId, Difficulty(difficulty), toggleExtend == 1); // include expired instances if we are toggling extend on + if (!instanceBind || !instanceBind->save || !instanceBind->perm) + return; + + BindExtensionState newState; + if (!toggleExtend || instanceBind->extendState == EXTEND_STATE_EXPIRED) + newState = EXTEND_STATE_NORMAL; + else + newState = EXTEND_STATE_EXTENDED; + + player->BindToInstance(instanceBind->save, true, newState, false); + } + /* InstancePlayerBind* instanceBind = _player->GetBoundInstance(mapId, Difficulty(difficulty)); if (!instanceBind || !instanceBind->save) diff --git a/src/server/game/Instances/InstanceSaveMgr.cpp b/src/server/game/Instances/InstanceSaveMgr.cpp index 9a64335e6f0..3538262ed35 100644 --- a/src/server/game/Instances/InstanceSaveMgr.cpp +++ b/src/server/game/Instances/InstanceSaveMgr.cpp @@ -439,6 +439,23 @@ void InstanceSaveManager::LoadResetTimes() } } +time_t InstanceSaveManager::GetSubsequentResetTime(uint32 mapid, Difficulty difficulty, time_t resetTime) const +{ + MapDifficulty const* mapDiff = GetMapDifficultyData(mapid, difficulty); + if (!mapDiff || !mapDiff->resetTime) + { + TC_LOG_ERROR("misc", "InstanceSaveManager::GetSubsequentResetTime: not valid difficulty or no reset delay for map %u", mapid); + return 0; + } + + time_t diff = sWorld->getIntConfig(CONFIG_INSTANCE_RESET_TIME_HOUR) * HOUR; + time_t period = uint32(((mapDiff->resetTime * sWorld->getRate(RATE_INSTANCE_RESET_TIME)) / DAY) * DAY); + if (period < DAY) + period = DAY; + + return ((resetTime + MINUTE) / DAY * DAY) + period + diff; +} + void InstanceSaveManager::ScheduleReset(bool add, time_t time, InstResetEvent event) { if (!add) @@ -476,6 +493,17 @@ void InstanceSaveManager::ScheduleReset(bool add, time_t time, InstResetEvent ev m_resetTimeQueue.insert(std::pair<time_t, InstResetEvent>(time, event)); } +void InstanceSaveManager::ForceGlobalReset(uint32 mapId, Difficulty difficulty) +{ + if (!GetDownscaledMapDifficultyData(mapId, difficulty)) + return; + // remove currently scheduled reset times + ScheduleReset(false, 0, InstResetEvent(1, mapId, difficulty, 0)); + ScheduleReset(false, 0, InstResetEvent(4, mapId, difficulty, 0)); + // force global reset on the instance + _ResetOrWarnAll(mapId, difficulty, false, time(nullptr)); +} + void InstanceSaveManager::Update() { time_t now = time(NULL); @@ -516,10 +544,26 @@ void InstanceSaveManager::_ResetSave(InstanceSaveHashMap::iterator &itr) // do not allow UnbindInstance to automatically unload the InstanceSaves lock_instLists = true; + bool shouldDelete = true; InstanceSave::PlayerListType &pList = itr->second->m_playerList; - while (!pList.empty()) + std::vector<Player*> temp; // list of expired binds that should be unbound + for (Player* player : pList) + { + if (InstancePlayerBind* bind = player->GetBoundInstance(itr->second->GetMapId(), itr->second->GetDifficulty())) + { + ASSERT(bind->save == itr->second); + if (bind->perm && bind->extendState) // permanent and not already expired + { + // actual promotion in DB already happened in caller + bind->extendState = bind->extendState == EXTEND_STATE_EXTENDED ? EXTEND_STATE_NORMAL : EXTEND_STATE_EXPIRED; + shouldDelete = false; + continue; + } + } + temp.push_back(player); + } + for (Player* player : temp) { - Player* player = *(pList.begin()); player->UnbindInstance(itr->second->GetMapId(), itr->second->GetDifficulty(), true); } @@ -530,8 +574,13 @@ void InstanceSaveManager::_ResetSave(InstanceSaveHashMap::iterator &itr) group->UnbindInstance(itr->second->GetMapId(), itr->second->GetDifficulty(), true); } - delete itr->second; - m_instanceSaveById.erase(itr++); + if (shouldDelete) + { + delete itr->second; + itr = m_instanceSaveById.erase(itr); + } + else + ++itr; lock_instLists = false; } @@ -572,31 +621,21 @@ void InstanceSaveManager::_ResetOrWarnAll(uint32 mapid, Difficulty difficulty, b MapEntry const* mapEntry = sMapStore.LookupEntry(mapid); if (!mapEntry->Instanceable()) return; + TC_LOG_DEBUG("misc", "InstanceSaveManager::ResetOrWarnAll: Processing map %s (%u) on difficulty %u (warn? %u)", mapEntry->name[0], mapid, uint8(difficulty), warn); time_t now = time(NULL); if (!warn) { - MapDifficulty const* mapDiff = GetMapDifficultyData(mapid, difficulty); - if (!mapDiff || !mapDiff->resetTime) - { - TC_LOG_ERROR("misc", "InstanceSaveManager::ResetOrWarnAll: not valid difficulty or no reset delay for map %d", mapid); + // calculate the next reset time + time_t next_reset = GetSubsequentResetTime(mapid, difficulty, resetTime); + if (!next_reset) return; - } - // remove all binds to instances of the given map - for (InstanceSaveHashMap::iterator itr = m_instanceSaveById.begin(); itr != m_instanceSaveById.end();) - { - if (itr->second->GetMapId() == mapid && itr->second->GetDifficulty() == difficulty) - _ResetSave(itr); - else - ++itr; - } - - // delete them from the DB, even if not loaded + // delete/promote instance binds from the DB, even if not loaded SQLTransaction trans = CharacterDatabase.BeginTransaction(); - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_MAP_DIFF); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_EXPIRED_CHAR_INSTANCE_BY_MAP_DIFF); stmt->setUInt16(0, uint16(mapid)); stmt->setUInt8(1, uint8(difficulty)); trans->Append(stmt); @@ -606,21 +645,26 @@ void InstanceSaveManager::_ResetOrWarnAll(uint32 mapid, Difficulty difficulty, b stmt->setUInt8(1, uint8(difficulty)); trans->Append(stmt); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INSTANCE_BY_MAP_DIFF); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_EXPIRED_INSTANCE_BY_MAP_DIFF); stmt->setUInt16(0, uint16(mapid)); stmt->setUInt8(1, uint8(difficulty)); trans->Append(stmt); - CharacterDatabase.CommitTransaction(trans); - - // calculate the next reset time - uint32 diff = sWorld->getIntConfig(CONFIG_INSTANCE_RESET_TIME_HOUR) * HOUR; + stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_EXPIRE_CHAR_INSTANCE_BY_MAP_DIFF); + stmt->setUInt16(0, uint16(mapid)); + stmt->setUInt8(1, uint8(difficulty)); + trans->Append(stmt); - uint32 period = uint32(((mapDiff->resetTime * sWorld->getRate(RATE_INSTANCE_RESET_TIME))/DAY) * DAY); - if (period < DAY) - period = DAY; + CharacterDatabase.CommitTransaction(trans); - uint32 next_reset = uint32(((resetTime + MINUTE) / DAY * DAY) + period + diff); + // promote loaded binds to instances of the given map + for (InstanceSaveHashMap::iterator itr = m_instanceSaveById.begin(); itr != m_instanceSaveById.end();) + { + if (itr->second->GetMapId() == mapid && itr->second->GetDifficulty() == difficulty) + _ResetSave(itr); + else + ++itr; + } SetResetTimeFor(mapid, difficulty, next_reset); ScheduleReset(true, time_t(next_reset-3600), InstResetEvent(1, mapid, difficulty, 0)); diff --git a/src/server/game/Instances/InstanceSaveMgr.h b/src/server/game/Instances/InstanceSaveMgr.h index e3d8175cbc4..d2b3237b3cf 100644 --- a/src/server/game/Instances/InstanceSaveMgr.h +++ b/src/server/game/Instances/InstanceSaveMgr.h @@ -190,6 +190,7 @@ class InstanceSaveManager ResetTimeByMapDifficultyMap::const_iterator itr = m_resetTimeByMapDifficulty.find(MAKE_PAIR32(mapid, d)); return itr != m_resetTimeByMapDifficulty.end() ? itr->second : 0; } + time_t GetSubsequentResetTime(uint32 mapid, Difficulty difficulty, time_t resetTime) const; // Use this on startup when initializing reset times void InitializeResetTimeFor(uint32 mapid, Difficulty d, time_t t) @@ -210,6 +211,7 @@ class InstanceSaveManager return m_resetTimeByMapDifficulty; } void ScheduleReset(bool add, time_t time, InstResetEvent event); + void ForceGlobalReset(uint32 mapId, Difficulty difficulty); void Update(); diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 1560d4bdd08..ccc599b6de8 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -37,7 +37,7 @@ #include "VMapFactory.h" u_map_magic MapMagic = { {'M','A','P','S'} }; -u_map_magic MapVersionMagic = { {'v','1','.','7'} }; +u_map_magic MapVersionMagic = { {'v','1','.','8'} }; u_map_magic MapAreaMagic = { {'A','R','E','A'} }; u_map_magic MapHeightMagic = { {'M','H','G','T'} }; u_map_magic MapLiquidMagic = { {'M','L','I','Q'} }; @@ -1805,10 +1805,10 @@ bool GridMap::loadHeightData(FILE* in, uint32 offset, uint32 /*size*/) if (header.flags & MAP_HEIGHT_HAS_FLIGHT_BOUNDS) { - _maxHeight = new float[16 * 16]; - _minHeight = new float[16 * 16]; - if (fread(_maxHeight, sizeof(float), 16 * 16, in) != 16 * 16 || - fread(_minHeight, sizeof(float), 16 * 16, in) != 16 * 16) + _maxHeight = new int16[3 * 3]; + _minHeight = new int16[3 * 3]; + if (fread(_maxHeight, sizeof(int16), 3 * 3, in) != 3 * 3 || + fread(_minHeight, sizeof(int16), 3 * 3, in) != 3 * 3) return false; } @@ -2087,11 +2087,59 @@ float GridMap::getMinHeight(float x, float y) const if (!_minHeight) return -500.0f; - x = 16 * (CENTER_GRID_ID - x / SIZE_OF_GRIDS); - y = 16 * (CENTER_GRID_ID - y / SIZE_OF_GRIDS); - int lx = (int)x & 15; - int ly = (int)y & 15; - return _minHeight[lx * 16 + ly]; + static uint32 const indices[] = + { + 3, 0, 4, + 0, 1, 4, + 1, 2, 4, + 2, 5, 4, + 5, 8, 4, + 8, 7, 4, + 7, 6, 4, + 6, 3, 4 + }; + + static float const boundGridCoords[] = + { + 0.0f, 0.0f, + 0.0f, -266.66666f, + 0.0f, -533.33331f, + -266.66666f, 0.0f, + -266.66666f, -266.66666f, + -266.66666f, -533.33331f, + -533.33331f, 0.0f, + -533.33331f, -266.66666f, + -533.33331f, -533.33331f + }; + + Cell cell(x, y); + float gx = x - (int32(cell.GridX()) - CENTER_GRID_ID + 1) * SIZE_OF_GRIDS; + float gy = y - (int32(cell.GridY()) - CENTER_GRID_ID + 1) * SIZE_OF_GRIDS; + + uint32 quarterIndex = 0; + if (cell.CellY() < MAX_NUMBER_OF_CELLS / 2) + { + if (cell.CellX() < MAX_NUMBER_OF_CELLS / 2) + { + quarterIndex = 4 + (gy > gx); + } + else + quarterIndex = 2 + ((-SIZE_OF_GRIDS - gx) > gy); + } + else if (cell.CellX() < MAX_NUMBER_OF_CELLS / 2) + { + quarterIndex = 6 + ((-SIZE_OF_GRIDS - gx) <= gy); + } + else + quarterIndex = gx > gy; + + quarterIndex *= 3; + + return G3D::Plane( + G3D::Vector3(boundGridCoords[indices[quarterIndex + 0] * 2 + 0], boundGridCoords[indices[quarterIndex + 0] * 2 + 1], _minHeight[indices[quarterIndex + 0]]), + G3D::Vector3(boundGridCoords[indices[quarterIndex + 1] * 2 + 0], boundGridCoords[indices[quarterIndex + 1] * 2 + 1], _minHeight[indices[quarterIndex + 1]]), + G3D::Vector3(boundGridCoords[indices[quarterIndex + 2] * 2 + 0], boundGridCoords[indices[quarterIndex + 2] * 2 + 1], _minHeight[indices[quarterIndex + 2]]) + ).distance(G3D::Vector3(gx, gy, 0.0f)); } float GridMap::getLiquidLevel(float x, float y) const @@ -2380,7 +2428,7 @@ uint32 Map::GetAreaId(float x, float y, float z, bool *isOutdoors) const atEntry = sAreaTableStore.LookupEntry(wmoEntry->areaId); } - uint32 areaId; + uint32 areaId = 0; if (atEntry) areaId = atEntry->ID; @@ -2388,8 +2436,9 @@ uint32 Map::GetAreaId(float x, float y, float z, bool *isOutdoors) const { if (GridMap* gmap = const_cast<Map*>(this)->GetGrid(x, y)) areaId = gmap->getArea(x, y); + // this used while not all *.map files generated (instances) - else + if (!areaId) areaId = i_mapEntry->linked_zone; } @@ -3209,22 +3258,37 @@ bool InstanceMap::Reset(uint8 method) } else { + bool doUnload = true; if (method == INSTANCE_RESET_GLOBAL) + { // set the homebind timer for players inside (1 minute) for (MapRefManager::iterator itr = m_mapRefManager.begin(); itr != m_mapRefManager.end(); ++itr) - itr->GetSource()->m_InstanceValid = false; + { + InstancePlayerBind* bind = itr->GetSource()->GetBoundInstance(GetId(), GetDifficulty()); + if (bind && bind->extendState && bind->save->GetInstanceId() == GetInstanceId()) + doUnload = false; + else + itr->GetSource()->m_InstanceValid = false; + } - // the unload timer is not started - // instead the map will unload immediately after the players have left - m_unloadWhenEmpty = true; - m_resetAfterUnload = true; + if (doUnload && HasPermBoundPlayers()) // check if any unloaded players have a nonexpired save to this + doUnload = false; + } + + if (doUnload) + { + // the unload timer is not started + // instead the map will unload immediately after the players have left + m_unloadWhenEmpty = true; + m_resetAfterUnload = true; + } } } else { // unloaded at next update m_unloadTimer = MIN_UNLOAD_DELAY; - m_resetAfterUnload = true; + m_resetAfterUnload = !(method == INSTANCE_RESET_GLOBAL && HasPermBoundPlayers()); } return m_mapRefManager.isEmpty(); @@ -3305,6 +3369,13 @@ MapDifficulty const* Map::GetMapDifficulty() const return GetMapDifficultyData(GetId(), GetDifficulty()); } +bool InstanceMap::HasPermBoundPlayers() const +{ + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PERM_BIND_BY_INSTANCE); + stmt->setUInt16(0,GetInstanceId()); + return !!CharacterDatabase.Query(stmt); +} + uint32 InstanceMap::GetMaxPlayers() const { MapDifficulty const* mapDiff = GetMapDifficulty(); diff --git a/src/server/game/Maps/Map.h b/src/server/game/Maps/Map.h index 13d48db0f9d..bc2bf72f271 100644 --- a/src/server/game/Maps/Map.h +++ b/src/server/game/Maps/Map.h @@ -167,8 +167,8 @@ class GridMap uint16* m_uint16_V8; uint8* m_uint8_V8; }; - float* _maxHeight; - float* _minHeight; + int16* _maxHeight; + int16* _minHeight; // Height level data float _gridHeight; float _gridIntHeightMultiplier; @@ -771,6 +771,9 @@ class InstanceMap : public Map void SendResetWarnings(uint32 timeLeft) const; void SetResetSchedule(bool on); + /* this checks if any players have a permanent bind (included reactivatable expired binds) to the instance ID + it needs a DB query, so use sparingly */ + bool HasPermBoundPlayers() const; uint32 GetMaxPlayers() const; uint32 GetMaxResetDelay() const; diff --git a/src/server/game/Movement/MotionMaster.cpp b/src/server/game/Movement/MotionMaster.cpp index 3fcae1398f9..05948b987ad 100644 --- a/src/server/game/Movement/MotionMaster.cpp +++ b/src/server/game/Movement/MotionMaster.cpp @@ -366,10 +366,10 @@ void MotionMaster::MoveJumpTo(float angle, float speedXY, float speedZ) float moveTimeHalf = speedZ / Movement::gravity; float dist = 2 * moveTimeHalf * speedXY; _owner->GetClosePoint(x, y, z, _owner->GetObjectSize(), dist, angle); - MoveJump(x, y, z, speedXY, speedZ); + MoveJump(x, y, z, 0.0f, speedXY, speedZ); } -void MotionMaster::MoveJump(float x, float y, float z, float speedXY, float speedZ, uint32 id) +void MotionMaster::MoveJump(float x, float y, float z, float o, float speedXY, float speedZ, uint32 id, bool hasOrientation /* = false*/) { TC_LOG_DEBUG("misc", "Unit (GUID: %u) jump to point (X: %f Y: %f Z: %f)", _owner->GetGUID().GetCounter(), x, y, z); if (speedXY <= 0.1f) @@ -382,6 +382,8 @@ void MotionMaster::MoveJump(float x, float y, float z, float speedXY, float spee init.MoveTo(x, y, z, false); init.SetParabolic(max_height, 0); init.SetVelocity(speedXY); + if (hasOrientation) + init.SetFacing(o); init.Launch(); Mutate(new EffectMovementGenerator(id), MOTION_SLOT_CONTROLLED); } diff --git a/src/server/game/Movement/MotionMaster.h b/src/server/game/Movement/MotionMaster.h index ce0f1f7836e..76ae12986d5 100644 --- a/src/server/game/Movement/MotionMaster.h +++ b/src/server/game/Movement/MotionMaster.h @@ -181,9 +181,11 @@ class MotionMaster //: private std::stack<MovementGenerator *> void MoveCharge(PathGenerator const& path, float speed = SPEED_CHARGE); void MoveKnockbackFrom(float srcX, float srcY, float speedXY, float speedZ); void MoveJumpTo(float angle, float speedXY, float speedZ); - void MoveJump(Position const& pos, float speedXY, float speedZ, uint32 id = EVENT_JUMP) - { MoveJump(pos.m_positionX, pos.m_positionY, pos.m_positionZ, speedXY, speedZ, id); } - void MoveJump(float x, float y, float z, float speedXY, float speedZ, uint32 id = EVENT_JUMP); + void MoveJump(Position const& pos, float speedXY, float speedZ, uint32 id = EVENT_JUMP, bool hasOrientation = false) + { + MoveJump(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), speedXY, speedZ, id, hasOrientation); + } + void MoveJump(float x, float y, float z, float o, float speedXY, float speedZ, uint32 id = EVENT_JUMP, bool hasOrientation = false); void MoveCirclePath(float x, float y, float z, float radius, bool clockwise, uint8 stepCount); void MoveSmoothPath(uint32 pointId, G3D::Vector3 const* pathPoints, size_t pathSize, bool walk); void MoveFall(uint32 id = 0); diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 3fa16cf4517..4b26ba96c9b 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -37,7 +37,6 @@ // namespace // { - UnusedScriptContainer UnusedScripts; UnusedScriptNamesContainer UnusedScriptNames; // } @@ -107,8 +106,9 @@ class ScriptRegistry // The actual list of scripts. This will be accessed concurrently, so it must not be modified // after server startup. static ScriptMap ScriptPointerList; + static std::vector<TScript*> Scripts; - static void AddScript(TScript* const script) + static void AddScript(TScript* const script, bool addToDeleteContainer = true) { ASSERT(script); @@ -126,6 +126,8 @@ class ScriptRegistry } AddScript(is_script_database_bound<TScript>{}, script); + if (addToDeleteContainer) + Scripts.push_back(script); } // Gets a script by its ID (assigned by ObjectMgr). @@ -186,11 +188,6 @@ class ScriptRegistry { // The script uses a script name from database, but isn't assigned to anything. TC_LOG_ERROR("sql.sql", "Script named '%s' does not have a script name assigned in database.", script->GetName().c_str()); - - // Avoid calling "delete script;" because we are currently in the script constructor - // In a valid scenario this will not happen because every script has a name assigned in the database - UnusedScripts.push_back(script); - return; } } @@ -210,6 +207,7 @@ class ScriptRegistry #define SCR_REG_MAP(T) ScriptRegistry<T>::ScriptMap #define SCR_REG_ITR(T) ScriptRegistry<T>::ScriptMapIterator #define SCR_REG_LST(T) ScriptRegistry<T>::ScriptPointerList +#define SCR_REG_VEC(T) ScriptRegistry<T>::Scripts // Utility macros for looping over scripts. #define FOR_SCRIPTS(T, C, E) \ @@ -266,17 +264,15 @@ void ScriptMgr::Initialize() } #endif - UnloadUnusedScripts(); - TC_LOG_INFO("server.loading", ">> Loaded %u C++ scripts in %u ms", GetScriptCount(), GetMSTimeDiffToNow(oldMSTime)); } void ScriptMgr::Unload() { #define SCR_CLEAR(T) \ - for (SCR_REG_ITR(T) itr = SCR_REG_LST(T).begin(); itr != SCR_REG_LST(T).end(); ++itr) \ - delete itr->second; \ - SCR_REG_LST(T).clear(); + for (T* scr : SCR_REG_VEC(T)) \ + delete scr; \ + SCR_REG_VEC(T).clear(); // Clear scripts for every script type. SCR_CLEAR(SpellScriptLoader); @@ -308,19 +304,10 @@ void ScriptMgr::Unload() #undef SCR_CLEAR - UnloadUnusedScripts(); - delete[] SpellSummary; delete[] UnitAI::AISpellInfo; } -void ScriptMgr::UnloadUnusedScripts() -{ - for (size_t i = 0; i < UnusedScripts.size(); ++i) - delete UnusedScripts[i]; - UnusedScripts.clear(); -} - void ScriptMgr::LoadDatabase() { sScriptSystemMgr->LoadScriptWaypoints(); @@ -1368,9 +1355,9 @@ void ScriptMgr::OnPlayerSave(Player* player) FOREACH_SCRIPT(PlayerScript)->OnSave(player); } -void ScriptMgr::OnPlayerBindToInstance(Player* player, Difficulty difficulty, uint32 mapid, bool permanent) +void ScriptMgr::OnPlayerBindToInstance(Player* player, Difficulty difficulty, uint32 mapid, bool permanent, uint8 extendState) { - FOREACH_SCRIPT(PlayerScript)->OnBindToInstance(player, difficulty, mapid, permanent); + FOREACH_SCRIPT(PlayerScript)->OnBindToInstance(player, difficulty, mapid, permanent, extendState); } void ScriptMgr::OnPlayerUpdateZone(Player* player, uint32 newZone, uint32 newArea) @@ -1555,8 +1542,7 @@ FormulaScript::FormulaScript(const char* name) UnitScript::UnitScript(const char* name, bool addToScripts) : ScriptObject(name) { - if (addToScripts) - ScriptRegistry<UnitScript>::AddScript(this); + ScriptRegistry<UnitScript>::AddScript(this, addToScripts); } WorldMapScript::WorldMapScript(const char* name, uint32 mapId) @@ -1696,6 +1682,7 @@ GroupScript::GroupScript(const char* name) // Instantiate static members of ScriptRegistry. template<class TScript> std::map<uint32, TScript*> ScriptRegistry<TScript>::ScriptPointerList; +template<class TScript> std::vector<TScript*> ScriptRegistry<TScript>::Scripts; template<class TScript> uint32 ScriptRegistry<TScript>::_scriptIdCounter = 0; // Specialize for each script type class like so: diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 5dfc0be688a..8a00305b4da 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -734,7 +734,7 @@ class PlayerScript : public UnitScript virtual void OnSave(Player* /*player*/) { } // Called when a player is bound to an instance - virtual void OnBindToInstance(Player* /*player*/, Difficulty /*difficulty*/, uint32 /*mapId*/, bool /*permanent*/) { } + virtual void OnBindToInstance(Player* /*player*/, Difficulty /*difficulty*/, uint32 /*mapId*/, bool /*permanent*/, uint8 /*extendState*/) { } // Called when a player switches to a new zone virtual void OnUpdateZone(Player* /*player*/, uint32 /*newZone*/, uint32 /*newArea*/) { } @@ -843,10 +843,7 @@ class GroupScript : public ScriptObject // namespace // { - typedef std::vector<ScriptObject*> UnusedScriptContainer; typedef std::list<std::string> UnusedScriptNamesContainer; - - extern UnusedScriptContainer UnusedScripts; extern UnusedScriptNamesContainer UnusedScriptNames; // } @@ -878,7 +875,6 @@ class ScriptMgr public: /* Unloading */ void Unload(); - void UnloadUnusedScripts(); public: /* SpellScriptLoader */ @@ -1054,7 +1050,7 @@ class ScriptMgr void OnPlayerDelete(ObjectGuid guid, uint32 accountId); void OnPlayerFailedDelete(ObjectGuid guid, uint32 accountId); void OnPlayerSave(Player* player); - void OnPlayerBindToInstance(Player* player, Difficulty difficulty, uint32 mapid, bool permanent); + void OnPlayerBindToInstance(Player* player, Difficulty difficulty, uint32 mapid, bool permanent, uint8 extendState); void OnPlayerUpdateZone(Player* player, uint32 newZone, uint32 newArea); void OnQuestStatusChange(Player* player, uint32 questId, QuestStatus status); diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index b72ceecfff0..207908c6d51 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -4683,11 +4683,6 @@ void AuraEffect::HandleAuraDummy(AuraApplication const* aurApp, uint8 mode, bool if (target->GetTypeId() == TYPEID_PLAYER) target->ToPlayer()->RemoveAmmo(); // not use ammo and not allow use break; - case 52916: // Honor Among Thieves - if (target->GetTypeId() == TYPEID_PLAYER) - if (Unit* spellTarget = ObjectAccessor::GetUnit(*target, target->ToPlayer()->GetComboTarget())) - target->CastSpell(spellTarget, 51699, true); - break; case 71563: if (Aura* newAura = target->AddAura(71564, target)) newAura->SetStackAmount(newAura->GetSpellInfo()->StackAmount); diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 7c1ccbdb463..23e2f144ff2 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -2429,7 +2429,7 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target) if (missInfo != SPELL_MISS_EVADE && !m_caster->IsFriendlyTo(unit) && (!m_spellInfo->IsPositive() || m_spellInfo->HasEffect(SPELL_EFFECT_DISPEL))) { - m_caster->CombatStart(unit, !m_spellInfo->HasAttribute(SPELL_ATTR3_NO_INITIAL_AGGRO)); + m_caster->CombatStart(unit, m_spellInfo->HasInitialAggro()); if (!unit->IsStandState()) unit->SetStandState(UNIT_STAND_STATE_STAND); @@ -2535,7 +2535,7 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, bool scaleA if (m_caster->GetTypeId() == TYPEID_PLAYER) m_caster->ToPlayer()->UpdatePvP(true); } - if (unit->IsInCombat() && !m_spellInfo->HasAttribute(SPELL_ATTR3_NO_INITIAL_AGGRO)) + if (unit->IsInCombat() && m_spellInfo->HasInitialAggro()) { m_caster->SetInCombatState(unit->GetCombatTimer() > 0, unit); unit->getHostileRefManager().threatAssist(m_caster, 0.0f); @@ -4611,8 +4611,7 @@ void Spell::HandleThreatSpells() if (m_UniqueTargetInfo.empty()) return; - if (m_spellInfo->HasAttribute(SPELL_ATTR1_NO_THREAT) || - m_spellInfo->HasAttribute(SPELL_ATTR3_NO_INITIAL_AGGRO)) + if (!m_spellInfo->HasInitialAggro()) return; float threat = 0.0f; @@ -4623,7 +4622,7 @@ void Spell::HandleThreatSpells() threat += threatEntry->flatMod; } - else if (m_spellInfo->HasAttribute(SPELL_ATTR0_CU_NO_INITIAL_THREAT) == 0) + else if (!m_spellInfo->HasAttribute(SPELL_ATTR0_CU_NO_INITIAL_THREAT)) threat += m_spellInfo->SpellLevel; // past this point only multiplicative effects occur diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index b2aa44e28df..9535ca291eb 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -1021,7 +1021,7 @@ void Spell::EffectJump(SpellEffIndex effIndex) float speedXY, speedZ; CalculateJumpSpeeds(effIndex, m_caster->GetExactDist2d(x, y), speedXY, speedZ); - m_caster->GetMotionMaster()->MoveJump(x, y, z, speedXY, speedZ); + m_caster->GetMotionMaster()->MoveJump(x, y, z, 0.0f, speedXY, speedZ, EVENT_JUMP, false); } void Spell::EffectJumpDest(SpellEffIndex effIndex) @@ -1035,13 +1035,9 @@ void Spell::EffectJumpDest(SpellEffIndex effIndex) if (!m_targets.HasDst()) return; - // Init dest coordinates - float x, y, z; - destTarget->GetPosition(x, y, z); - float speedXY, speedZ; - CalculateJumpSpeeds(effIndex, m_caster->GetExactDist2d(x, y), speedXY, speedZ); - m_caster->GetMotionMaster()->MoveJump(x, y, z, speedXY, speedZ); + CalculateJumpSpeeds(effIndex, m_caster->GetExactDist2d(destTarget), speedXY, speedZ); + m_caster->GetMotionMaster()->MoveJump(*destTarget, speedXY, speedZ, EVENT_JUMP, true); } void Spell::CalculateJumpSpeeds(uint8 i, float dist, float & speedXY, float & speedZ) @@ -4903,7 +4899,7 @@ void Spell::EffectPullTowards(SpellEffIndex effIndex) float speedXY = float(m_spellInfo->Effects[effIndex].MiscValue) * 0.1f; float speedZ = unitTarget->GetDistance(pos) / speedXY * 0.5f * Movement::gravity; - unitTarget->GetMotionMaster()->MoveJump(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), speedXY, speedZ); + unitTarget->GetMotionMaster()->MoveJump(pos, speedXY, speedZ); } void Spell::EffectDispelMechanic(SpellEffIndex effIndex) diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index 699e4857ee0..069c794ca8b 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -1180,6 +1180,11 @@ bool SpellInfo::IsAutoRepeatRangedSpell() const return HasAttribute(SPELL_ATTR2_AUTOREPEAT_FLAG); } +bool SpellInfo::HasInitialAggro() const +{ + return !(HasAttribute(SPELL_ATTR1_NO_THREAT) || HasAttribute(SPELL_ATTR3_NO_INITIAL_AGGRO)); +} + bool SpellInfo::IsAffectedBySpellMods() const { return !HasAttribute(SPELL_ATTR3_NO_DONE_BONUS); diff --git a/src/server/game/Spells/SpellInfo.h b/src/server/game/Spells/SpellInfo.h index dbbab5fb30b..ba658c885fa 100644 --- a/src/server/game/Spells/SpellInfo.h +++ b/src/server/game/Spells/SpellInfo.h @@ -420,6 +420,7 @@ public: bool IsBreakingStealth() const; bool IsRangedWeaponSpell() const; bool IsAutoRepeatRangedSpell() const; + bool HasInitialAggro() const; bool IsAffectedBySpellMods() const; bool IsAffectedBySpellMod(SpellModifier const* mod) const; diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 40e8f2d4e24..f5bb1c920fe 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -2987,13 +2987,6 @@ void SpellMgr::LoadSpellInfoCorrections() case 36350: // They Must Burn Bomb Aura (self) spellInfo->Effects[EFFECT_0].TriggerSpell = 36325; // They Must Burn Bomb Drop (DND) break; - case 49838: // Stop Time - case 69438: // Sample Satisfaction - case 69445: // Perfume Spritz - case 69489: // Chocolate Sample - case 69563: // Cologne Spritz - spellInfo->AttributesEx3 |= SPELL_ATTR3_NO_INITIAL_AGGRO; - break; case 61407: // Energize Cores case 62136: // Energize Cores case 54069: // Energize Cores diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp index 9902b83ff63..b937fc4e0a4 100644 --- a/src/server/scripts/Commands/cs_debug.cpp +++ b/src/server/scripts/Commands/cs_debug.cpp @@ -93,7 +93,8 @@ public: { "moveflags", rbac::RBAC_PERM_COMMAND_DEBUG_MOVEFLAGS, false, &HandleDebugMoveflagsCommand, "" }, { "transport", rbac::RBAC_PERM_COMMAND_DEBUG_TRANSPORT, false, &HandleDebugTransportCommand, "" }, { "loadcells", rbac::RBAC_PERM_COMMAND_DEBUG_LOADCELLS, false, &HandleDebugLoadCellsCommand, "" }, - { "boundary", rbac::RBAC_PERM_COMMAND_DEBUG_BOUNDARY, false, &HandleDebugBoundaryCommand, "" } + { "boundary", rbac::RBAC_PERM_COMMAND_DEBUG_BOUNDARY, false, &HandleDebugBoundaryCommand, "" }, + { "raidreset", rbac::RBAC_PERM_COMMAND_INSTANCE_UNBIND, false, &HandleDebugRaidResetCommand, "" } }; static std::vector<ChatCommand> commandTable = { @@ -1442,6 +1443,32 @@ public: return true; } + + static bool HandleDebugRaidResetCommand(ChatHandler* /*handler*/, char const* args) + { + char* map_str = args ? strtok((char*)args, " ") : nullptr; + char* difficulty_str = args ? strtok(nullptr, " ") : nullptr; + + int32 map = map_str ? atoi(map_str) : -1; + if (map <= 0) + return false; + MapEntry const* mEntry = sMapStore.LookupEntry(map); + if (!mEntry || !mEntry->IsRaid()) + return false; + int32 difficulty = difficulty_str ? atoi(difficulty_str) : -1; + if (difficulty >= MAX_RAID_DIFFICULTY || difficulty < -1) + return false; + + if (difficulty == -1) + for (uint8 diff = 0; diff < MAX_RAID_DIFFICULTY; ++diff) + { + if (GetMapDifficultyData(mEntry->MapID, Difficulty(diff))) + sInstanceSaveMgr->ForceGlobalReset(mEntry->MapID, Difficulty(diff)); + } + else + sInstanceSaveMgr->ForceGlobalReset(mEntry->MapID, Difficulty(difficulty)); + return true; + } }; void AddSC_debug_commandscript() diff --git a/src/server/scripts/Commands/cs_instance.cpp b/src/server/scripts/Commands/cs_instance.cpp index a53d1f00b54..d0325a317db 100644 --- a/src/server/scripts/Commands/cs_instance.cpp +++ b/src/server/scripts/Commands/cs_instance.cpp @@ -82,7 +82,7 @@ public: { InstanceSave* save = itr->second.save; std::string timeleft = GetTimeString(save->GetResetTime() - time(NULL)); - handler->PSendSysMessage(LANG_COMMAND_LIST_BIND_INFO, itr->first, save->GetInstanceId(), itr->second.perm ? "yes" : "no", save->GetDifficulty(), save->CanReset() ? "yes" : "no", timeleft.c_str()); + handler->PSendSysMessage(LANG_COMMAND_LIST_BIND_INFO, itr->first, save->GetInstanceId(), itr->second.perm ? "yes" : "no", itr->second.extendState == EXTEND_STATE_EXPIRED ? "expired" : itr->second.extendState == EXTEND_STATE_EXTENDED ? "yes" : "no", save->GetDifficulty(), save->CanReset() ? "yes" : "no", timeleft.c_str()); counter++; } } @@ -98,7 +98,7 @@ public: { InstanceSave* save = itr->second.save; std::string timeleft = GetTimeString(save->GetResetTime() - time(NULL)); - handler->PSendSysMessage(LANG_COMMAND_LIST_BIND_INFO, itr->first, save->GetInstanceId(), itr->second.perm ? "yes" : "no", save->GetDifficulty(), save->CanReset() ? "yes" : "no", timeleft.c_str()); + handler->PSendSysMessage(LANG_COMMAND_LIST_BIND_INFO, itr->first, save->GetInstanceId(), itr->second.perm ? "yes" : "no", "-", save->GetDifficulty(), save->CanReset() ? "yes" : "no", timeleft.c_str()); counter++; } } diff --git a/src/server/scripts/EasternKingdoms/MagistersTerrace/instance_magisters_terrace.cpp b/src/server/scripts/EasternKingdoms/MagistersTerrace/instance_magisters_terrace.cpp index b48f0edec25..6386bb50e1a 100644 --- a/src/server/scripts/EasternKingdoms/MagistersTerrace/instance_magisters_terrace.cpp +++ b/src/server/scripts/EasternKingdoms/MagistersTerrace/instance_magisters_terrace.cpp @@ -16,8 +16,10 @@ */ #include "ScriptMgr.h" +#include "ScriptedCreature.h" #include "InstanceScript.h" #include "magisters_terrace.h" +#include "EventMap.h" /* 0 - Selin Fireheart @@ -36,6 +38,8 @@ DoorData const doorData[] = { 0, 0, DOOR_TYPE_ROOM } // END }; +Position const KalecgosSpawnPos = { 164.3747f, -397.1197f, 2.151798f, 1.66219f }; + class instance_magisters_terrace : public InstanceMapScript { public: @@ -93,6 +97,10 @@ class instance_magisters_terrace : public InstanceMapScript case NPC_DELRISSA: DelrissaGUID = creature->GetGUID(); break; + case NPC_KALECGOS: + case NPC_HUMAN_KALECGOS: + KalecgosGUID = creature->GetGUID(); + break; default: break; } @@ -139,6 +147,25 @@ class instance_magisters_terrace : public InstanceMapScript } } + void ProcessEvent(WorldObject* obj, uint32 eventId) override + { + if (eventId == EVENT_SPAWN_KALECGOS) + if (!ObjectAccessor::GetCreature(*obj, KalecgosGUID) && Events.Empty()) + Events.ScheduleEvent(EVENT_SPAWN_KALECGOS, Minutes(1)); + } + + void Update(uint32 diff) override + { + Events.Update(diff); + + if (Events.ExecuteEvent() == EVENT_SPAWN_KALECGOS) + if (Creature* kalecgos = instance->SummonCreature(NPC_KALECGOS, KalecgosSpawnPos)) + { + kalecgos->GetMotionMaster()->MovePath(PATH_KALECGOS_FLIGHT, false); + kalecgos->AI()->Talk(SAY_KALECGOS_SPAWN); + } + } + bool SetBossState(uint32 type, EncounterState state) override { if (!InstanceScript::SetBossState(type, state)) @@ -177,10 +204,12 @@ class instance_magisters_terrace : public InstanceMapScript } protected: + EventMap Events; ObjectGuid SelinGUID; ObjectGuid DelrissaGUID; ObjectGuid KaelStatue[2]; ObjectGuid EscapeOrbGUID; + ObjectGuid KalecgosGUID; uint32 DelrissaDeathCount; }; diff --git a/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.cpp b/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.cpp index e216a024468..5b90ac8ccf4 100644 --- a/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.cpp +++ b/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.cpp @@ -31,7 +31,8 @@ EndContentData */ #include "ScriptedCreature.h" #include "ScriptedGossip.h" #include "Player.h" -#include "SpellInfo.h" +#include "magisters_terrace.h" +#include "EventMap.h" /*###### ## npc_kalecgos @@ -39,30 +40,29 @@ EndContentData */ enum Spells { - SPELL_TRANSFORM_TO_KAEL = 44670, + SPELL_KALECGOS_TRANSFORM = 44670, + SPELL_TRANSFORM_VISUAL = 24085, + SPELL_CAMERA_SHAKE = 44762, SPELL_ORB_KILL_CREDIT = 46307 }; -enum Creatures +enum MovementPoints { - NPC_KAEL = 24848 //human form entry + POINT_ID_PREPARE_LANDING = 6 }; -enum Misc +enum EventIds { - POINT_ID_LAND = 1 + EVENT_KALECGOS_TRANSFORM = 1, + EVENT_KALECGOS_LANDING = 2 }; -const float afKaelLandPoint[] = {225.045f, -276.236f, -5.434f}; - #define GOSSIP_ITEM_KAEL_1 "Who are you?" #define GOSSIP_ITEM_KAEL_2 "What can we do to assist you?" #define GOSSIP_ITEM_KAEL_3 "What brings you to the Sunwell?" #define GOSSIP_ITEM_KAEL_4 "You're not alone here?" #define GOSSIP_ITEM_KAEL_5 "What would Kil'jaeden want with a mortal woman?" -// This is friendly keal that appear after used Orb. -// If we assume DB handle summon, summon appear somewhere outside the platform where Orb is class npc_kalecgos : public CreatureScript { public: @@ -115,52 +115,46 @@ public: struct npc_kalecgosAI : public ScriptedAI { - npc_kalecgosAI(Creature* creature) : ScriptedAI(creature) - { - Initialize(); - } + npc_kalecgosAI(Creature* creature) : ScriptedAI(creature) { } - void Initialize() + void MovementInform(uint32 type, uint32 pointId) override { - m_uiTransformTimer = 0; - } - - uint32 m_uiTransformTimer; - - void Reset() override - { - Initialize(); - - // we must assume he appear as dragon somewhere outside the platform of orb, and then move directly to here - if (me->GetEntry() != NPC_KAEL) - me->GetMotionMaster()->MovePoint(POINT_ID_LAND, afKaelLandPoint[0], afKaelLandPoint[1], afKaelLandPoint[2]); - } - - void MovementInform(uint32 uiType, uint32 uiPointId) override - { - if (uiType != POINT_MOTION_TYPE) + if (type != WAYPOINT_MOTION_TYPE) return; - if (uiPointId == POINT_ID_LAND) - m_uiTransformTimer = MINUTE*IN_MILLISECONDS; + if (pointId == POINT_ID_PREPARE_LANDING) + { + me->HandleEmoteCommand(EMOTE_ONESHOT_LAND); + me->SetDisableGravity(false); + me->SetHover(false); + events.ScheduleEvent(EVENT_KALECGOS_LANDING, Seconds(2)); + } } - void UpdateAI(uint32 uiDiff) override + void UpdateAI(uint32 diff) override { - if (m_uiTransformTimer) + events.Update(diff); + + switch (events.ExecuteEvent()) { - if (m_uiTransformTimer <= uiDiff) - { + case EVENT_KALECGOS_LANDING: + DoCastAOE(SPELL_CAMERA_SHAKE); + me->SetObjectScale(0.6f); + events.ScheduleEvent(EVENT_KALECGOS_TRANSFORM, Seconds(1)); + break; + case EVENT_KALECGOS_TRANSFORM: DoCast(me, SPELL_ORB_KILL_CREDIT, true); - - // Transform and update entry, now ready for quest/read gossip - DoCast(me, SPELL_TRANSFORM_TO_KAEL, false); - me->UpdateEntry(NPC_KAEL); - - m_uiTransformTimer = 0; - } else m_uiTransformTimer -= uiDiff; + DoCast(me, SPELL_TRANSFORM_VISUAL, false); + DoCast(me, SPELL_KALECGOS_TRANSFORM, false); + me->UpdateEntry(NPC_HUMAN_KALECGOS); + break; + default: + break; } } + + private: + EventMap events; }; }; diff --git a/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.h b/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.h index 917ad0eb50b..05718dfc1dd 100644 --- a/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.h +++ b/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.h @@ -42,7 +42,9 @@ enum CreatureIds { NPC_SELIN = 24723, NPC_DELRISSA = 24560, - NPC_FEL_CRYSTAL = 24722 + NPC_FEL_CRYSTAL = 24722, + NPC_KALECGOS = 24844, + NPC_HUMAN_KALECGOS = 24848 }; enum GameObjectIds @@ -57,4 +59,19 @@ enum GameObjectIds GO_ESCAPE_ORB = 188173 }; +enum InstanceEventIds +{ + EVENT_SPAWN_KALECGOS = 16547 +}; + +enum InstanceText +{ + SAY_KALECGOS_SPAWN = 0 +}; + +enum MovementData +{ + PATH_KALECGOS_FLIGHT = 248440 +}; + #endif diff --git a/src/server/scripts/EasternKingdoms/zone_undercity.cpp b/src/server/scripts/EasternKingdoms/zone_undercity.cpp index c0b4e06cfff..99556587e65 100644 --- a/src/server/scripts/EasternKingdoms/zone_undercity.cpp +++ b/src/server/scripts/EasternKingdoms/zone_undercity.cpp @@ -157,7 +157,7 @@ public: { if (Creature* target = ObjectAccessor::GetCreature(*summoned, targetGUID)) { - target->GetMotionMaster()->MoveJump(target->GetPositionX(), target->GetPositionY(), me->GetPositionZ() + 15.0f, 0); + target->GetMotionMaster()->MoveJump(target->GetPositionX(), target->GetPositionY(), me->GetPositionZ() + 15.0f, me->GetOrientation(), 0); target->SetPosition(target->GetPositionX(), target->GetPositionY(), me->GetPositionZ()+15.0f, 0.0f); summoned->CastSpell(target, SPELL_RIBBON_OF_SOULS, false); } diff --git a/src/server/scripts/EasternKingdoms/zone_western_plaguelands.cpp b/src/server/scripts/EasternKingdoms/zone_western_plaguelands.cpp index b1a00f35bb5..a01b93a140b 100644 --- a/src/server/scripts/EasternKingdoms/zone_western_plaguelands.cpp +++ b/src/server/scripts/EasternKingdoms/zone_western_plaguelands.cpp @@ -25,7 +25,6 @@ EndScriptData */ /* ContentData npcs_dithers_and_arbington -npc_myranda_the_hag npc_the_scourge_cauldron npc_andorhal_tower EndContentData */ @@ -33,7 +32,6 @@ EndContentData */ #include "ScriptMgr.h" #include "ScriptedCreature.h" #include "ScriptedGossip.h" -#include "ScriptedEscortAI.h" #include "Player.h" #include "WorldSession.h" @@ -120,54 +118,6 @@ public: }; /*###### -## npc_myranda_the_hag -######*/ - -enum Myranda -{ - ILLUSION_GOSSIP = 4773, - QUEST_SUBTERFUGE = 5862, - QUEST_IN_DREAMS = 5944, - SPELL_SCARLET_ILLUSION = 17961 -}; - -class npc_myranda_the_hag : public CreatureScript -{ -public: - npc_myranda_the_hag() : CreatureScript("npc_myranda_the_hag") { } - - bool OnGossipSelect(Player* player, Creature* /*creature*/, uint32 /*sender*/, uint32 action) override - { - player->PlayerTalkClass->ClearMenus(); - if (action == GOSSIP_ACTION_INFO_DEF + 1) - { - player->CLOSE_GOSSIP_MENU(); - player->CastSpell(player, SPELL_SCARLET_ILLUSION, false); - } - return true; - } - - bool OnGossipHello(Player* player, Creature* creature) override - { - if (creature->IsQuestGiver()) - player->PrepareQuestMenu(creature->GetGUID()); - - if (player->GetQuestStatus(QUEST_SUBTERFUGE) == QUEST_STATUS_COMPLETE && - player->GetQuestStatus(QUEST_IN_DREAMS) != QUEST_STATUS_COMPLETE && - !player->HasAura(SPELL_SCARLET_ILLUSION)) - { - player->ADD_GOSSIP_ITEM_DB(Player::GetDefaultGossipMenuForSource(creature), 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); - player->SEND_GOSSIP_MENU(ILLUSION_GOSSIP, creature->GetGUID()); - return true; - } - else - player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); - - return true; - } -}; - -/*###### ## npc_the_scourge_cauldron ######*/ @@ -195,7 +145,7 @@ public: me->DealDamage(me, me->GetHealth(), NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false); //override any database `spawntimesecs` to prevent duplicated summons uint32 rTime = me->GetRespawnDelay(); - if (rTime<600) + if (rTime < 600) me->SetRespawnDelay(600); } @@ -274,7 +224,6 @@ public: } void MoveInLineOfSight(Unit* who) override - { if (!who || who->GetTypeId() != TYPEID_PLAYER) return; @@ -286,14 +235,9 @@ public: }; }; -/*###### -## -######*/ - void AddSC_western_plaguelands() { new npcs_dithers_and_arbington(); - new npc_myranda_the_hag(); new npc_the_scourge_cauldron(); new npc_andorhal_tower(); } diff --git a/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_general_zarithrian.cpp b/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_general_zarithrian.cpp index fb5642c6f3b..84d7d92acfe 100644 --- a/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_general_zarithrian.cpp +++ b/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_general_zarithrian.cpp @@ -135,9 +135,9 @@ class boss_general_zarithrian : public CreatureScript Talk(SAY_KILL); } - bool CanAIAttack(Unit const* /*target*/) const override + bool CanAIAttack(Unit const* target) const override { - return (instance->GetBossState(DATA_SAVIANA_RAGEFIRE) == DONE && instance->GetBossState(DATA_BALTHARUS_THE_WARBORN) == DONE); + return (instance->GetBossState(DATA_SAVIANA_RAGEFIRE) == DONE && instance->GetBossState(DATA_BALTHARUS_THE_WARBORN) == DONE && BossAI::CanAIAttack(target)); } void UpdateAI(uint32 diff) override diff --git a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp index 1e860fb6ec5..6c1b516c7de 100644 --- a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp +++ b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp @@ -461,13 +461,13 @@ class boss_toc_champion_controller : public CreatureScript if (playerTeam == ALLIANCE) { temp->SetHomePosition(vChampionJumpTarget[pos].GetPositionX(), vChampionJumpTarget[pos].GetPositionY(), vChampionJumpTarget[pos].GetPositionZ(), 0); - temp->GetMotionMaster()->MoveJump(vChampionJumpTarget[pos].GetPositionX(), vChampionJumpTarget[pos].GetPositionY(), vChampionJumpTarget[pos].GetPositionZ(), 20.0f, 20.0f); + temp->GetMotionMaster()->MoveJump(vChampionJumpTarget[pos], 20.0f, 20.0f); temp->SetOrientation(0); } else { temp->SetHomePosition((ToCCommonLoc[1].GetPositionX()*2)-vChampionJumpTarget[pos].GetPositionX(), vChampionJumpTarget[pos].GetPositionY(), vChampionJumpTarget[pos].GetPositionZ(), 3); - temp->GetMotionMaster()->MoveJump((ToCCommonLoc[1].GetPositionX()*2)-vChampionJumpTarget[pos].GetPositionX(), vChampionJumpTarget[pos].GetPositionY(), vChampionJumpTarget[pos].GetPositionZ(), 20.0f, 20.0f); + temp->GetMotionMaster()->MoveJump((ToCCommonLoc[1].GetPositionX() * 2) - vChampionJumpTarget[pos].GetPositionX(), vChampionJumpTarget[pos].GetPositionY(), vChampionJumpTarget[pos].GetPositionZ(), vChampionJumpTarget[pos].GetOrientation(), 20.0f, 20.0f); temp->SetOrientation(3); } } diff --git a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_northrend_beasts.cpp b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_northrend_beasts.cpp index 83daa6e35a2..e3720503d0a 100644 --- a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_northrend_beasts.cpp +++ b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_northrend_beasts.cpp @@ -390,7 +390,7 @@ class npc_snobold_vassal : public CreatureScript else if (Unit* target2 = SelectTarget(SELECT_TARGET_RANDOM, 0, 0.0f, true)) { _targetGUID = target2->GetGUID(); - me->GetMotionMaster()->MoveJump(target2->GetPositionX(), target2->GetPositionY(), target2->GetPositionZ(), 15.0f, 15.0f); + me->GetMotionMaster()->MoveJump(*target2, 15.0f, 15.0f); } } } @@ -981,7 +981,7 @@ class boss_icehowl : public CreatureScript events.ScheduleEvent(EVENT_WHIRL, urand(15*IN_MILLISECONDS, 30*IN_MILLISECONDS)); return; case EVENT_MASSIVE_CRASH: - me->GetMotionMaster()->MoveJump(ToCCommonLoc[1].GetPositionX(), ToCCommonLoc[1].GetPositionY(), ToCCommonLoc[1].GetPositionZ(), 20.0f, 20.0f, 0); // 1: Middle of the room + me->GetMotionMaster()->MoveJump(ToCCommonLoc[1], 20.0f, 20.0f, 0); // 1: Middle of the room SetCombatMovement(false); me->AttackStop(); _stage = 7; //Invalid (Do nothing more than move) @@ -1034,7 +1034,7 @@ class boss_icehowl : public CreatureScript _trampleTargetY = target->GetPositionY(); _trampleTargetZ = target->GetPositionZ(); // 2: Hop Backwards - me->GetMotionMaster()->MoveJump(2*me->GetPositionX() - _trampleTargetX, 2*me->GetPositionY() - _trampleTargetY, me->GetPositionZ(), 30.0f, 20.0f, 0); + me->GetMotionMaster()->MoveJump(2*me->GetPositionX() - _trampleTargetX, 2*me->GetPositionY() - _trampleTargetY, me->GetPositionZ(), me->GetOrientation(), 30.0f, 20.0f, 0); _stage = 7; //Invalid (Do nothing more than move) } else diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_deathbringer_saurfang.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_deathbringer_saurfang.cpp index 7e9083115b8..3ea0de31764 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_deathbringer_saurfang.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_deathbringer_saurfang.cpp @@ -598,7 +598,7 @@ class boss_deathbringer_saurfang : public CreatureScript if (target->GetTransport()) return false; - return true; + return BossAI::CanAIAttack(target); } static uint32 const FightWonValue; diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp index da028aad643..9fa624aaad3 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp @@ -580,7 +580,7 @@ class boss_the_lich_king : public CreatureScript bool CanAIAttack(Unit const* target) const override { // The Lich King must not select targets in frostmourne room if he killed everyone outside - return !target->HasAura(SPELL_IN_FROSTMOURNE_ROOM); + return !target->HasAura(SPELL_IN_FROSTMOURNE_ROOM) && BossAI::CanAIAttack(target); } void EnterEvadeMode(EvadeReason /*why*/) override diff --git a/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp b/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp index 81f1e071da0..be12894ebea 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp @@ -375,8 +375,12 @@ class boss_gothik : public CreatureScript _gateIsOpen = true; for (ObjectGuid summonGuid : summons) + { if (Creature* summon = ObjectAccessor::GetCreature(*me, summonGuid)) summon->AI()->DoAction(ACTION_GATE_OPENED); + if (summons.empty()) // ACTION_GATE_OPENED may cause an evade, despawning summons and invalidating our iterator + break; + } } void DamageTaken(Unit* /*who*/, uint32& damage) override diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp index 01c4704592f..6dfc3bf01e7 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp @@ -1768,9 +1768,7 @@ class spell_vehicle_throw_passenger : public SpellScriptLoader else { passenger->ExitVehicle(); - float x, y, z; - targets.GetDstPos()->GetPosition(x, y, z); - passenger->GetMotionMaster()->MoveJump(x, y, z, targets.GetSpeedXY(), targets.GetSpeedZ()); + passenger->GetMotionMaster()->MoveJump(*targets.GetDstPos(), targets.GetSpeedXY(), targets.GetSpeedZ()); } } } diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp index f435c669935..e694433c614 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp @@ -444,7 +444,7 @@ class boss_mimiron : public CreatureScript DoCastAOE(SPELL_DESPAWN_ASSAULT_BOTS); me->ExitVehicle(); // ExitVehicle() offset position is not implemented, so we make up for that with MoveJump()... - me->GetMotionMaster()->MoveJump(me->GetPositionX() + (10.f * std::cos(me->GetOrientation())), me->GetPositionY() + (10.f * std::sin(me->GetOrientation())), me->GetPositionZ(), 10.f, 5.f); + me->GetMotionMaster()->MoveJump(me->GetPositionX() + (10.f * std::cos(me->GetOrientation())), me->GetPositionY() + (10.f * std::sin(me->GetOrientation())), me->GetPositionZ(), me->GetOrientation(), 10.f, 5.f); events.ScheduleEvent(EVENT_OUTTRO_1, 7000); } diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp b/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp index 77b376bf7d5..d7b65093898 100644 --- a/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp +++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp @@ -238,7 +238,7 @@ public: me->SetInCombatWithZone(); instance->SetBossState(DATA_SKADI_THE_RUTHLESS, IN_PROGRESS); instance->DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_TIMED_START_EVENT); - me->GetMotionMaster()->MoveJump(Location[0].GetPositionX(), Location[0].GetPositionY(), Location[0].GetPositionZ(), 5.0f, 10.0f); + me->GetMotionMaster()->MoveJump(Location[0], 5.0f, 10.0f); me->SetWalk(false); m_uiMountTimer = 1000; Summons.DespawnEntry(NPC_GRAUF); @@ -289,7 +289,7 @@ public: pGrauf->GetMotionMaster()->MoveFall(); pGrauf->HandleEmoteCommand(EMOTE_ONESHOT_FLYDEATH); } - me->GetMotionMaster()->MoveJump(Location[4].GetPositionX(), Location[4].GetPositionY(), Location[4].GetPositionZ(), 5.0f, 10.0f); + me->GetMotionMaster()->MoveJump(Location[4], 5.0f, 10.0f); me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_NON_ATTACKABLE); Talk(SAY_DRAKE_DEATH); m_uiCrushTimer = 8000; diff --git a/src/server/scripts/Northrend/isle_of_conquest.cpp b/src/server/scripts/Northrend/isle_of_conquest.cpp index 14d862eca39..68121e940c9 100644 --- a/src/server/scripts/Northrend/isle_of_conquest.cpp +++ b/src/server/scripts/Northrend/isle_of_conquest.cpp @@ -206,7 +206,7 @@ class spell_ioc_parachute_ic : public SpellScriptLoader class StartLaunchEvent : public BasicEvent { public: - StartLaunchEvent(float x, float y, float z, ObjectGuid::LowType lowGuid) : _x(x), _y(y), _z(z), _lowGuid(lowGuid) + StartLaunchEvent(Position const& pos, ObjectGuid::LowType lowGuid) : _pos(pos), _lowGuid(lowGuid) { } @@ -218,15 +218,15 @@ class StartLaunchEvent : public BasicEvent player->AddAura(SPELL_LAUNCH_NO_FALLING_DAMAGE, player); // prevents falling damage float speedZ = 10.0f; - float dist = player->GetExactDist2d(_x, _y); + float dist = player->GetExactDist2d(&_pos); player->ExitVehicle(); - player->GetMotionMaster()->MoveJump(_x, _y, _z, dist, speedZ); + player->GetMotionMaster()->MoveJump(_pos, dist, speedZ, EVENT_JUMP, true); return true; } private: - float _x, _y, _z; + Position _pos; ObjectGuid::LowType _lowGuid; }; @@ -244,11 +244,7 @@ class spell_ioc_launch : public SpellScriptLoader if (!GetCaster()->ToCreature() || !GetExplTargetDest()) return; - float x, y, z; - x = GetExplTargetDest()->GetPositionX(); - y = GetExplTargetDest()->GetPositionY(); - z = GetExplTargetDest()->GetPositionZ(); - GetCaster()->ToCreature()->m_Events.AddEvent(new StartLaunchEvent(x, y, z, GetHitPlayer()->GetGUID().GetCounter()), GetCaster()->ToCreature()->m_Events.CalculateTime(2500)); + GetCaster()->ToCreature()->m_Events.AddEvent(new StartLaunchEvent(*GetExplTargetDest(), GetHitPlayer()->GetGUID().GetCounter()), GetCaster()->ToCreature()->m_Events.CalculateTime(2500)); } void Register() override diff --git a/src/server/scripts/Outland/TempestKeep/Eye/boss_kaelthas.cpp b/src/server/scripts/Outland/TempestKeep/Eye/boss_kaelthas.cpp index e5812390bd2..be27932e6b4 100644 --- a/src/server/scripts/Outland/TempestKeep/Eye/boss_kaelthas.cpp +++ b/src/server/scripts/Outland/TempestKeep/Eye/boss_kaelthas.cpp @@ -144,6 +144,7 @@ enum Spells // Thaladred the Darkener spells SPELL_PSYCHIC_BLOW = 10689, SPELL_SILENCE = 30225, + SPELL_REND = 36965, // Lord Sanguinar spells SPELL_BELLOWING_ROAR = 40636, // Grand Astromancer Capernian spells @@ -881,11 +882,13 @@ class boss_thaladred_the_darkener : public CreatureScript { Gaze_Timer = 100; Silence_Timer = 20000; + Rend_Timer = 4000; PsychicBlow_Timer = 10000; } uint32 Gaze_Timer; uint32 Silence_Timer; + uint32 Rend_Timer; uint32 PsychicBlow_Timer; void Reset() override @@ -939,6 +942,15 @@ class boss_thaladred_the_darkener : public CreatureScript else Silence_Timer -= diff; + //Rend_Timer + if (Rend_Timer <= diff) + { + DoCastVictim(SPELL_REND); + Rend_Timer = 4000; + } + else + Rend_Timer -= diff; + //PsychicBlow_Timer if (PsychicBlow_Timer <= diff) { diff --git a/src/server/scripts/Spells/spell_druid.cpp b/src/server/scripts/Spells/spell_druid.cpp index 7b853c36075..c088ae07e66 100644 --- a/src/server/scripts/Spells/spell_druid.cpp +++ b/src/server/scripts/Spells/spell_druid.cpp @@ -991,6 +991,37 @@ class spell_dru_swift_flight_passive : public SpellScriptLoader } }; +// -33943 - Flight Form +class spell_dru_flight_form : public SpellScriptLoader +{ + public: + spell_dru_flight_form() : SpellScriptLoader("spell_dru_flight_form") { } + + class spell_dru_flight_form_SpellScript : public SpellScript + { + PrepareSpellScript(spell_dru_flight_form_SpellScript); + + SpellCastResult CheckCast() + { + Unit* caster = GetCaster(); + if (caster->IsInDisallowedMountForm()) + return SPELL_FAILED_NOT_SHAPESHIFT; + + return SPELL_CAST_OK; + } + + void Register() override + { + OnCheckCast += SpellCheckCastFn(spell_dru_flight_form_SpellScript::CheckCast); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_dru_flight_form_SpellScript(); + } +}; + // -5217 - Tiger's Fury class spell_dru_tiger_s_fury : public SpellScriptLoader { @@ -1196,6 +1227,7 @@ void AddSC_druid_spell_scripts() new spell_dru_starfall_dummy(); new spell_dru_survival_instincts(); new spell_dru_swift_flight_passive(); + new spell_dru_flight_form(); new spell_dru_tiger_s_fury(); new spell_dru_typhoon(); new spell_dru_t10_restoration_4p_bonus(); diff --git a/src/server/scripts/Spells/spell_rogue.cpp b/src/server/scripts/Spells/spell_rogue.cpp index 3ca2db40d65..9b577d4e140 100644 --- a/src/server/scripts/Spells/spell_rogue.cpp +++ b/src/server/scripts/Spells/spell_rogue.cpp @@ -41,6 +41,9 @@ enum RogueSpells SPELL_ROGUE_SHIV_TRIGGERED = 5940, SPELL_ROGUE_TRICKS_OF_THE_TRADE_DMG_BOOST = 57933, SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC = 59628, + SPELL_ROGUE_HONOR_AMONG_THIEVES = 51698, + SPELL_ROGUE_HONOR_AMONG_THIEVES_PROC = 52916, + SPELL_ROGUE_HONOR_AMONG_THIEVES_2 = 51699 }; // 13877, 33735, (check 51211, 65956) - Blade Flurry @@ -703,6 +706,143 @@ class spell_rog_tricks_of_the_trade_proc : public SpellScriptLoader } }; +// 51698,51700,51701 - Honor Among Thieves +class spell_rog_honor_among_thieves : public SpellScriptLoader +{ +public: + spell_rog_honor_among_thieves() : SpellScriptLoader("spell_rog_honor_among_thieves") { } + + class spell_rog_honor_among_thieves_AuraScript : public AuraScript + { + PrepareAuraScript(spell_rog_honor_among_thieves_AuraScript); + + bool CheckProc(ProcEventInfo& /*eventInfo*/) + { + Unit* caster = GetCaster(); + if (!caster) + return false; + + if (!caster->GetSpellHistory()->HasCooldown(GetSpellInfo()->Effects[EFFECT_0].TriggerSpell)) + return true; + + return false; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + + Unit* caster = GetCaster(); + if (!caster) + return; + + Unit* target = GetTarget(); + target->CastSpell(target, GetSpellInfo()->Effects[EFFECT_0].TriggerSpell, TriggerCastFlags(TRIGGERED_FULL_MASK & ~TRIGGERED_IGNORE_SPELL_AND_CATEGORY_CD), nullptr, aurEff, caster->GetGUID()); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_rog_honor_among_thieves_AuraScript::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_rog_honor_among_thieves_AuraScript::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_rog_honor_among_thieves_AuraScript(); + } +}; + +// 52916 - Honor Among Thieves (Proc) +class spell_rog_honor_among_thieves_proc : public SpellScriptLoader +{ +public: + spell_rog_honor_among_thieves_proc() : SpellScriptLoader("spell_rog_honor_among_thieves_proc") { } + + class spell_rog_honor_among_thieves_proc_SpellScript : public SpellScript + { + PrepareSpellScript(spell_rog_honor_among_thieves_proc_SpellScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + if (!sSpellMgr->GetSpellInfo(SPELL_ROGUE_HONOR_AMONG_THIEVES_PROC)) + return false; + + return true; + } + + void FilterTargets(std::list<WorldObject*>& targets) + { + targets.clear(); + + Unit* target = GetOriginalCaster(); + if (!target) + return; + + targets.push_back(target); + } + + void HandleBeforeHit() + { + Unit* target = GetHitUnit(); + if (!target) + return; + + /* + * The applied aura has a duration of 8 seconds + * This prevents new applications while its active + * Removing it on each new proc enables the application from different sources (different grouped players) + * and on new procs after the source cooldown is finished (1 second) + */ + if (target->HasAura(GetSpellInfo()->Id)) + target->RemoveAura(GetSpellInfo()->Id); + } + + void TriggerCooldown() + { + Unit* target = GetHitUnit(); + if (!target) + return; + + target->GetSpellHistory()->AddCooldown(GetSpellInfo()->Id, 0, std::chrono::seconds(1)); + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_rog_honor_among_thieves_proc_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_CASTER_AREA_PARTY); + BeforeHit += SpellHitFn(spell_rog_honor_among_thieves_proc_SpellScript::HandleBeforeHit); + AfterHit += SpellHitFn(spell_rog_honor_among_thieves_proc_SpellScript::TriggerCooldown); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_rog_honor_among_thieves_proc_SpellScript(); + } + + class spell_rog_honor_among_thieves_proc_AuraScript : public AuraScript + { + PrepareAuraScript(spell_rog_honor_among_thieves_proc_AuraScript); + + void HandleEffectApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + if (Player* player = GetTarget()->ToPlayer()) + if (Unit* spellTarget = ObjectAccessor::GetUnit(*player, player->GetTarget())) + player->CastSpell(spellTarget, SPELL_ROGUE_HONOR_AMONG_THIEVES_2, true); + } + + void Register() override + { + AfterEffectApply += AuraEffectApplyFn(spell_rog_honor_among_thieves_proc_AuraScript::HandleEffectApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL); + } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_rog_honor_among_thieves_proc_AuraScript(); + } +}; + void AddSC_rogue_spell_scripts() { new spell_rog_blade_flurry(); @@ -716,4 +856,6 @@ void AddSC_rogue_spell_scripts() new spell_rog_shiv(); new spell_rog_tricks_of_the_trade(); new spell_rog_tricks_of_the_trade_proc(); + new spell_rog_honor_among_thieves(); + new spell_rog_honor_among_thieves_proc(); } diff --git a/src/server/scripts/World/npcs_special.cpp b/src/server/scripts/World/npcs_special.cpp index 56d48949fac..80b4fac4333 100644 --- a/src/server/scripts/World/npcs_special.cpp +++ b/src/server/scripts/World/npcs_special.cpp @@ -39,6 +39,7 @@ npc_shadowfiend 100% restore 5% of owner's mana when shadowfiend die f npc_locksmith 75% list of keys needs to be confirmed npc_firework 100% NPC's summoned by rockets and rocket clusters, for making them cast visual npc_train_wrecker 100% Wind-Up Train Wrecker that kills train set +npc_egbert 100% Egbert run's around EndContentData */ #include "ScriptMgr.h" @@ -2192,7 +2193,7 @@ public: // Check if we are near Elune'ara lake south, if so try to summon Omen or a minion if (me->GetZoneId() == ZONE_MOONGLADE) { - if (!me->FindNearestCreature(NPC_OMEN, 100.0f, false) && me->GetDistance2d(omenSummonPos.GetPositionX(), omenSummonPos.GetPositionY()) <= 100.0f) + if (!me->FindNearestCreature(NPC_OMEN, 100.0f) && me->GetDistance2d(omenSummonPos.GetPositionX(), omenSummonPos.GetPositionY()) <= 100.0f) { switch (urand(0, 9)) { @@ -2574,8 +2575,8 @@ class npc_train_wrecker : public CreatureScript enum EgbertMisc { - EVENT_MOVE_POS = 1, - EVENT_RETURN = 2 + SPELL_EGBERT = 40669, + EVENT_RETURN = 3 }; class npc_egbert : public CreatureScript @@ -2583,9 +2584,9 @@ class npc_egbert : public CreatureScript public: npc_egbert() : CreatureScript("npc_egbert") {} - struct npc_egbertAI : public PetAI + struct npc_egbertAI : public NullCreatureAI { - npc_egbertAI(Creature* creature) : PetAI(creature) + npc_egbertAI(Creature* creature) : NullCreatureAI(creature) { if (Unit* owner = me->GetCharmerOrOwner()) if (owner->GetMap()->GetEntry()->addon > 1) @@ -2595,29 +2596,40 @@ public: void Reset() override { _events.Reset(); - _events.ScheduleEvent(EVENT_MOVE_POS, urandms(1, 20)); + if (Unit* owner = me->GetCharmerOrOwner()) + me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle()); + } + + void EnterEvadeMode(EvadeReason why) override + { + if (!_EnterEvadeMode(why)) + return; + + Reset(); } void UpdateAI(uint32 diff) override { _events.Update(diff); + if (Unit* owner = me->GetCharmerOrOwner()) + { + if (!me->IsWithinDist(owner, 40.f)) + { + me->RemoveAura(SPELL_EGBERT); + me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle()); + } + } + + if (me->HasAura(SPELL_EGBERT)) + _events.ScheduleEvent(EVENT_RETURN, urandms(5, 20)); + while (uint32 eventId = _events.ExecuteEvent()) { switch (eventId) { - case EVENT_MOVE_POS: - if (Unit* owner = me->GetCharmerOrOwner()) - { - me->GetMotionMaster()->Clear(); - me->GetMotionMaster()->MovePoint(0, owner->GetPositionX() + frand(-30.0f, 30.0f), owner->GetPositionY() + frand(-30.0f, 30.0f), owner->GetPositionZ()); - } - _events.ScheduleEvent(EVENT_RETURN, urandms(3, 4)); - break; case EVENT_RETURN: - if (Unit* owner = me->GetCharmerOrOwner()) - me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle()); - _events.ScheduleEvent(EVENT_MOVE_POS, urandms(1, 20)); + me->RemoveAura(SPELL_EGBERT); break; default: break; diff --git a/src/tools/map_extractor/System.cpp b/src/tools/map_extractor/System.cpp index 3694a2f9d41..f3a761fd437 100644 --- a/src/tools/map_extractor/System.cpp +++ b/src/tools/map_extractor/System.cpp @@ -257,7 +257,7 @@ void ReadLiquidTypeTableDBC() // Map file format data static char const* MAP_MAGIC = "MAPS"; -static char const* MAP_VERSION_MAGIC = "v1.7"; +static char const* MAP_VERSION_MAGIC = "v1.8"; static char const* MAP_AREA_MAGIC = "AREA"; static char const* MAP_HEIGHT_MAGIC = "MHGT"; static char const* MAP_LIQUID_MAGIC = "MLIQ"; @@ -348,8 +348,8 @@ uint8 liquid_flags[ADT_CELLS_PER_GRID][ADT_CELLS_PER_GRID]; bool liquid_show[ADT_GRID_SIZE][ADT_GRID_SIZE]; float liquid_height[ADT_GRID_SIZE+1][ADT_GRID_SIZE+1]; -float flight_box_max[ADT_CELLS_PER_GRID][ADT_CELLS_PER_GRID]; -float flight_box_min[ADT_CELLS_PER_GRID][ADT_CELLS_PER_GRID]; +int16 flight_box_max[3][3]; +int16 flight_box_min[3][3]; bool ConvertADT(std::string const& inputPath, std::string const& outputPath, int /*cell_y*/, int /*cell_x*/, uint32 build) { @@ -529,76 +529,8 @@ bool ConvertADT(std::string const& inputPath, std::string const& outputPath, int bool hasFlightBox = false; if (adt_MFBO* mfbo = adt.a_grid->getMFBO()) { - static uint32 const indices[] = - { - 3, 0, 4, - 0, 1, 4, - 1, 2, 4, - 2, 5, 4, - 5, 8, 4, - 8, 7, 4, - 7, 6, 4, - 6, 3, 4 - }; - - static float const boundGridCoords[] = - { - 0.0f, 0.0f, - 0.0f, -266.66666f, - 0.0f, -533.33331f, - -266.66666f, 0.0f, - -266.66666f, -266.66666f, - -266.66666f, -533.33331f, - -533.33331f, 0.0f, - -533.33331f, -266.66666f, - -533.33331f, -533.33331f - }; - - for (int gy = 0; gy < ADT_CELLS_PER_GRID; ++gy) - { - for (int gx = 0; gx < ADT_CELLS_PER_GRID; ++gx) - { - int32 quarterIndex = 0; - if (gy > ADT_CELLS_PER_GRID / 2) - { - if (gx > ADT_CELLS_PER_GRID / 2) - { - quarterIndex = 4 + gx < gy; - } - else - quarterIndex = 2; - } - else if (gx > ADT_CELLS_PER_GRID / 2) - { - quarterIndex = 7; - } - else - quarterIndex = gx > gy; - - quarterIndex *= 3; - G3D::Plane planeMax( - G3D::Vector3(boundGridCoords[indices[quarterIndex + 0] * 2 + 0], boundGridCoords[indices[quarterIndex + 0] * 2 + 1], mfbo->max.coords[indices[quarterIndex + 0]]), - G3D::Vector3(boundGridCoords[indices[quarterIndex + 1] * 2 + 0], boundGridCoords[indices[quarterIndex + 1] * 2 + 1], mfbo->max.coords[indices[quarterIndex + 1]]), - G3D::Vector3(boundGridCoords[indices[quarterIndex + 2] * 2 + 0], boundGridCoords[indices[quarterIndex + 2] * 2 + 1], mfbo->max.coords[indices[quarterIndex + 2]]) - ); - - G3D::Plane planeMin( - G3D::Vector3(boundGridCoords[indices[quarterIndex + 0] * 2 + 0], boundGridCoords[indices[quarterIndex + 0] * 2 + 1], mfbo->min.coords[indices[quarterIndex + 0]]), - G3D::Vector3(boundGridCoords[indices[quarterIndex + 1] * 2 + 0], boundGridCoords[indices[quarterIndex + 1] * 2 + 1], mfbo->min.coords[indices[quarterIndex + 1]]), - G3D::Vector3(boundGridCoords[indices[quarterIndex + 2] * 2 + 0], boundGridCoords[indices[quarterIndex + 2] * 2 + 1], mfbo->min.coords[indices[quarterIndex + 2]]) - ); - - auto non_nan_distance = [](G3D::Plane const& plane) { - auto d = plane.distance(G3D::Vector3(0.0f, 0.0f, 0.0f)); - assert(!G3D::isNaN(d)); - return d; - }; - - flight_box_max[gy][gx] = non_nan_distance(planeMax); - flight_box_min[gy][gx] = non_nan_distance(planeMin); - } - } - + memcpy(flight_box_max, &mfbo->max, sizeof(flight_box_max)); + memcpy(flight_box_min, &mfbo->min, sizeof(flight_box_min)); hasFlightBox = true; } diff --git a/src/tools/mmaps_generator/TerrainBuilder.cpp b/src/tools/mmaps_generator/TerrainBuilder.cpp index 70ab7fca0c9..69b1ffcb062 100644 --- a/src/tools/mmaps_generator/TerrainBuilder.cpp +++ b/src/tools/mmaps_generator/TerrainBuilder.cpp @@ -80,7 +80,7 @@ struct map_liquidHeader namespace MMAP { - char const* MAP_VERSION_MAGIC = "v1.7"; + char const* MAP_VERSION_MAGIC = "v1.8"; TerrainBuilder::TerrainBuilder(bool skipLiquid) : m_skipLiquid (skipLiquid){ } TerrainBuilder::~TerrainBuilder() { } |
