Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ed6eea2f43 | |||
| a9c5cacec8 | |||
| 146f02f046 | |||
| a2c532b676 | |||
| 25ad97555f | |||
| 40b395db8f | |||
| 9b7fb4dfdb | |||
| d9173eca3e | |||
| 36cde9a0bd | |||
| 8af2ddc596 | |||
| 316e886869 | |||
| a87eb41eac | |||
| a9fa07aab5 | |||
| 4c51bb778d | |||
| d0c3a62be2 |
@@ -26,6 +26,11 @@ services:
|
||||
ports: !override ["127.0.0.1:8085:8085/tcp"]
|
||||
environment:
|
||||
CONSOLE_ENABLE: "1"
|
||||
MOONWELL_VOICE_ENABLE: "1"
|
||||
MOONWELL_VOICE_ADDRESS: 127.0.0.1:3725
|
||||
MOONWELL_VOICE_FEED: host.docker.internal:3726
|
||||
extra_hosts: ["host.docker.internal:host-gateway"]
|
||||
secrets: [db_password, voice_secret]
|
||||
stdin_open: true
|
||||
tty: true
|
||||
volumes:
|
||||
@@ -40,3 +45,5 @@ secrets:
|
||||
file: ../local/secrets/db_password
|
||||
db_root_password:
|
||||
file: ../local/secrets/db_root_password
|
||||
voice_secret:
|
||||
file: ../local/secrets/voice_secret
|
||||
|
||||
@@ -3,7 +3,7 @@ set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
mkdir -p secrets data
|
||||
chmod 0700 secrets
|
||||
for secret in db_password db_root_password; do
|
||||
for secret in db_password db_root_password voice_secret; do
|
||||
if [[ ! -e "secrets/$secret" ]]; then
|
||||
(umask 077; openssl rand -hex 32 > "secrets/$secret")
|
||||
fi
|
||||
|
||||
@@ -9,3 +9,5 @@ BUILD_JOBS=2
|
||||
PARTY_BOT_MAX_BOTS=4
|
||||
# Fill battleground queues with bots when a real player queues.
|
||||
BATTLE_BOT_AUTO_JOIN=0
|
||||
# Quest item drop chance multiplier (1 = stock vanilla).
|
||||
RATE_DROP_ITEM_QUEST=2
|
||||
|
||||
@@ -22,7 +22,8 @@ mkdir -p secrets data
|
||||
chmod 0700 secrets
|
||||
(umask 077; openssl rand -hex 32 > secrets/db_password)
|
||||
(umask 077; openssl rand -hex 32 > secrets/db_root_password)
|
||||
chmod 0444 secrets/db_password secrets/db_root_password
|
||||
(umask 077; openssl rand -hex 32 > secrets/voice_secret)
|
||||
chmod 0444 secrets/db_password secrets/db_root_password secrets/voice_secret
|
||||
./prepare-db.sh
|
||||
# Необязательное дополнение к штатной русской локализации:
|
||||
# python3 prepare-russian.py
|
||||
@@ -212,3 +213,23 @@ world dump, всех миграций этого checkout и дополните
|
||||
healthy; TCP 3724 отвечает. Проверены генерация world-конфига и backup.
|
||||
Игровой вход, загрузка карт и полный запуск `mangosd` требуют клиентских
|
||||
данных и пока не проверены.
|
||||
|
||||
## Голосовой чат MoonWell
|
||||
|
||||
Голос идёт через отдельный контейнер `voice-relay` (UDP 3725, наружу), mangosd только выдаёт
|
||||
клиентам подписанные тикеты (`.mwvoice`) и передаёт relay положение, группу и гильдию каждого
|
||||
персонажа по внутренней сети compose. Голос в mangosd выключен, пока `.env` не включит его:
|
||||
|
||||
```bash
|
||||
# Бинарник relay собирается в репозитории клиента: cargo build --release -p moonwell-voice
|
||||
# --bin moonwell-voice-relay (под Linux), и кладётся сюда:
|
||||
mkdir -p voice && cp moonwell-voice-relay voice/ && chmod 0755 voice/moonwell-voice-relay
|
||||
(umask 077; openssl rand -hex 32 > secrets/voice_secret); chmod 0444 secrets/voice_secret
|
||||
# В .env:
|
||||
# MOONWELL_VOICE_ENABLE=1
|
||||
# MOONWELL_VOICE_ADDRESS=play.moon-well.online:3725
|
||||
docker compose up -d voice-relay mangosd
|
||||
```
|
||||
|
||||
Секрет `voice_secret` нужен compose всегда (он объявлен для mangosd), даже при выключенном голосе.
|
||||
Открыть в файрволе хоста нужно только UDP 3725.
|
||||
|
||||
@@ -69,6 +69,11 @@ services:
|
||||
TZ: Europe/Moscow
|
||||
PARTY_BOT_MAX_BOTS: ${PARTY_BOT_MAX_BOTS:-4}
|
||||
BATTLE_BOT_AUTO_JOIN: ${BATTLE_BOT_AUTO_JOIN:-0}
|
||||
RATE_DROP_ITEM_QUEST: ${RATE_DROP_ITEM_QUEST:-2}
|
||||
MOONWELL_VOICE_ENABLE: ${MOONWELL_VOICE_ENABLE:-0}
|
||||
MOONWELL_VOICE_ADDRESS: ${MOONWELL_VOICE_ADDRESS:-}
|
||||
MOONWELL_VOICE_FEED: voice-relay:3726
|
||||
secrets: [db_password, voice_secret]
|
||||
healthcheck:
|
||||
test: [CMD, python3, -c, "import socket; socket.create_connection(('127.0.0.1',8085),5).close()"]
|
||||
interval: 30s
|
||||
@@ -81,6 +86,22 @@ services:
|
||||
- ./data:/data:ro
|
||||
- ./entrypoint.py:/opt/entrypoint.py:ro
|
||||
- world_logs:/var/log/vmangos
|
||||
voice-relay:
|
||||
image: ubuntu:24.04@sha256:33ceb71981b602c1a7443a53469e4dba065f7503eab3078a2d7a57a2ab987517
|
||||
restart: unless-stopped
|
||||
init: true
|
||||
read_only: true
|
||||
cap_drop: [ALL]
|
||||
security_opt: [no-new-privileges:true]
|
||||
user: "10001:10001"
|
||||
command: [/opt/voice/moonwell-voice-relay, --listen, "0.0.0.0:3725", --feed, "0.0.0.0:3726", --secret-file, /run/secrets/voice_secret]
|
||||
secrets: [voice_secret]
|
||||
ports: ["3725:3725/udp"]
|
||||
volumes:
|
||||
- ./voice:/opt/voice:ro
|
||||
logging:
|
||||
driver: json-file
|
||||
options: {max-size: 10m, max-file: "5"}
|
||||
realm-config:
|
||||
image: mariadb:10.11@sha256:7db29378d4fdab73f8123bbc2b48905c90d1a4b00cf848b028f1e81e623257f2
|
||||
profiles: [tools]
|
||||
@@ -103,3 +124,5 @@ secrets:
|
||||
file: ./secrets/db_password
|
||||
db_root_password:
|
||||
file: ./secrets/db_root_password
|
||||
voice_secret:
|
||||
file: ./secrets/voice_secret
|
||||
|
||||
@@ -32,10 +32,31 @@ else:
|
||||
values[key] = str(value)
|
||||
values.update({"DataDir": '"/data"', "HonorDir": '"/var/log/vmangos"', "RealmID": "1", "RealmZone": "12", "DeclinedNames": "1", "Console.Enable": "0", "Ra.Enable": "0", "SOAP.Enabled": "0", "PlayerSave.Interval": "300000"})
|
||||
values.update({"Warden.WinEnabled": "0", "Warden.OSXEnabled": "0"})
|
||||
quest_drop = os.environ.get("RATE_DROP_ITEM_QUEST", "2")
|
||||
if not re.fullmatch(r"[0-9]{1,2}(\.[0-9]{1,2})?", quest_drop) or not 0 < float(quest_drop) <= 10:
|
||||
raise SystemExit("Invalid RATE_DROP_ITEM_QUEST")
|
||||
values["Rate.Drop.Item.Quest"] = quest_drop
|
||||
console = os.environ.get("CONSOLE_ENABLE", "0")
|
||||
if console not in ("0", "1"):
|
||||
raise SystemExit("Invalid CONSOLE_ENABLE")
|
||||
values["Console.Enable"] = console
|
||||
voice = os.environ.get("MOONWELL_VOICE_ENABLE", "0")
|
||||
if voice not in ("0", "1"):
|
||||
raise SystemExit("Invalid MOONWELL_VOICE_ENABLE")
|
||||
values["MoonWell.Voice.Enable"] = voice
|
||||
if voice == "1":
|
||||
secret_file = Path("/run/secrets/voice_secret")
|
||||
if not secret_file.is_file():
|
||||
raise SystemExit("MOONWELL_VOICE_ENABLE=1 needs the voice_secret secret")
|
||||
secret = secret_file.read_text().strip()
|
||||
if not re.fullmatch(r"[A-Za-z0-9]{32,}", secret):
|
||||
raise SystemExit("The voice secret must contain at least 32 alphanumeric characters")
|
||||
values["MoonWell.Voice.Secret"] = f'"{secret}"'
|
||||
for name, key in (("MOONWELL_VOICE_ADDRESS", "MoonWell.Voice.Address"), ("MOONWELL_VOICE_FEED", "MoonWell.Voice.Feed")):
|
||||
address = os.environ.get(name, "")
|
||||
if not re.fullmatch(r"[A-Za-z0-9.-]+:[0-9]{1,5}", address):
|
||||
raise SystemExit("Invalid " + name)
|
||||
values[key] = f'"{address}"'
|
||||
for directory in ("5875/dbc", "maps", "vmaps", "mmaps"):
|
||||
if not Path("/data", directory).is_dir():
|
||||
raise SystemExit("Missing client data: /data/" + directory)
|
||||
|
||||
@@ -23,6 +23,7 @@ set (game_SRCS
|
||||
Conditions.cpp
|
||||
CreatureGroups.cpp
|
||||
DungeonScaling.cpp
|
||||
MoonWellVoice.cpp
|
||||
GameEventMgr.cpp
|
||||
GuardMgr.cpp
|
||||
GMTicketMgr.cpp
|
||||
@@ -444,6 +445,7 @@ set (game_SRCS
|
||||
OutdoorPvP/OutdoorPvPSI.h
|
||||
PacketBroadcast/MovementBroadcaster.h
|
||||
PacketBroadcast/PlayerBroadcaster.h
|
||||
MoonWellVoice.h
|
||||
PlayerBots/CombatBotBaseAI.h
|
||||
PlayerBots/PartyBotAI.h
|
||||
PlayerBots/BattleBotAI.h
|
||||
|
||||
@@ -1240,6 +1240,7 @@ ChatCommand * ChatHandler::getCommandTable()
|
||||
{ "gocorpse", SEC_TICKETMASTER, false, &ChatHandler::HandleGocorpseCommand, "", nullptr },
|
||||
{ "commands", SEC_PLAYER, true, &ChatHandler::HandleCommandsCommand, "", nullptr },
|
||||
{ "mwquests", SEC_PLAYER, false, &ChatHandler::HandleMoonWellQuestsCommand, "", nullptr },
|
||||
{ "mwvoice", SEC_PLAYER, false, &ChatHandler::HandleMoonWellVoiceCommand, "", nullptr },
|
||||
{ "demorph", SEC_GAMEMASTER, false, &ChatHandler::HandleDeMorphCommand, "", nullptr },
|
||||
{ "namedie", SEC_GAMEMASTER, false, &ChatHandler::HandleNameDieCommand, "", nullptr },
|
||||
{ "die", SEC_GAMEMASTER, false, &ChatHandler::HandleDieCommand, "", nullptr },
|
||||
|
||||
@@ -936,6 +936,7 @@ class ChatHandler
|
||||
bool HandleHelpCommand(char* args);
|
||||
bool HandleCommandsCommand(char* args);
|
||||
bool HandleMoonWellQuestsCommand(char* args);
|
||||
bool HandleMoonWellVoiceCommand(char* args);
|
||||
bool HandleStartCommand(char* args);
|
||||
bool HandleMountCommand(char* args);
|
||||
bool HandleDismountCommand(char* args);
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "InstanceData.h"
|
||||
#include "MapManager.h"
|
||||
#include "BattleGroundMgr.h"
|
||||
#include "MoonWellVoice.h"
|
||||
|
||||
bool ChatHandler::HandleHelpCommand(char* args)
|
||||
{
|
||||
@@ -64,6 +65,14 @@ bool ChatHandler::HandleMoonWellQuestsCommand(char* /*args*/)
|
||||
return true;
|
||||
}
|
||||
|
||||
// .mwvoice: the MoonWell client asks for a voice ticket and the relay address (MoonWellVoice::SendTicket).
|
||||
bool ChatHandler::HandleMoonWellVoiceCommand(char* /*args*/)
|
||||
{
|
||||
if (Player* player = m_session ? m_session->GetPlayer() : nullptr)
|
||||
MoonWellVoice::SendTicket(player);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChatHandler::HandleAuctionAllianceCommand(char* /*args*/)
|
||||
{
|
||||
m_session->GetPlayer()->SetAuctionAccessMode(m_session->GetPlayer()->GetTeam() != ALLIANCE ? -1 : 0);
|
||||
|
||||
@@ -947,6 +947,9 @@ bool Group::CountRollVote(ObjectGuid const& playerGUID, Rolls::iterator& rollI,
|
||||
if (itr == roll->playerVote.end())
|
||||
return true; // result used for need iterator ++, so avoid for end of list
|
||||
|
||||
if (itr->second != ROLL_NOT_EMITED_YET)
|
||||
return false; // player already rolled for this loot!
|
||||
|
||||
if (Loot* pLoot = roll->getLoot())
|
||||
if (pLoot->items.empty())
|
||||
return false;
|
||||
|
||||
@@ -270,7 +270,10 @@ bool LootStoreItem::Roll(bool rate, float chanceMod) const
|
||||
|
||||
ItemPrototype const* pProto = sObjectMgr.GetItemPrototype(itemid);
|
||||
|
||||
float qualityModifier = pProto && rate ? sWorld.getConfig(qualityToRate[pProto->Quality]) : 1.0f;
|
||||
// MoonWell: a quest drop takes the quest rate in place of its quality's.
|
||||
float qualityModifier = !rate ? 1.0f
|
||||
: needs_quest ? sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_ITEM_QUEST)
|
||||
: pProto ? sWorld.getConfig(qualityToRate[pProto->Quality]) : 1.0f;
|
||||
|
||||
return roll_chance_f(chance * chanceMod * qualityModifier);
|
||||
}
|
||||
|
||||
@@ -701,7 +701,8 @@ uint32 MapManager::GetContinentInstanceId(uint32 mapId, float x, float y, bool*
|
||||
-10670.61f, -1692.51f,
|
||||
-10908.48f, -1563.87f,
|
||||
-13006.40f, -1622.80f,
|
||||
-12863.23f, -4798.42f
|
||||
-12863.23f, -4798.42f,
|
||||
-12863.23f, -5855.58f
|
||||
};
|
||||
static float const stormwindAreaSouthLimit[] = {
|
||||
-8725.337891f, 3535.624023f,
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "MoonWellVoice.h"
|
||||
#include "ByteBuffer.h"
|
||||
#include "Chat.h"
|
||||
#include "Config/Config.h"
|
||||
#include "Group.h"
|
||||
#include "Log.h"
|
||||
#include "Player.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "WorldSession.h"
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace MoonWellVoice
|
||||
{
|
||||
// Must match the relay: feed::MAX_PER_DATAGRAM keeps a datagram under a 1280-byte MTU.
|
||||
static uint8 const VERSION = 1;
|
||||
static size_t const MAC_LEN = 16;
|
||||
static size_t const MAX_PER_DATAGRAM = 31;
|
||||
// A failed socket or name lookup is retried this often, so the relay may start after mangosd.
|
||||
static uint32 const REOPEN_MS = 10 * IN_MILLISECONDS;
|
||||
|
||||
static bool s_enabled = false;
|
||||
static std::string s_secret;
|
||||
static std::string s_address;
|
||||
static std::string s_feed;
|
||||
static uint32 s_interval = 250;
|
||||
static uint32 s_ticketLifetime = HOUR;
|
||||
static uint32 s_sinceFeed = 0;
|
||||
static uint32 s_sinceOpen = REOPEN_MS;
|
||||
// What the feed did since the last report, logged every REPORT_MS when it changed, so a feed
|
||||
// that stops or fails shows in the log.
|
||||
static uint32 const REPORT_MS = 30 * IN_MILLISECONDS;
|
||||
static uint32 s_sinceReport = 0;
|
||||
static uint32 s_sent = 0;
|
||||
static uint32 s_failed = 0;
|
||||
static int s_lastError = 0;
|
||||
static uint32 s_lastCount = 0;
|
||||
#ifdef _WIN32
|
||||
static SOCKET s_socket = INVALID_SOCKET;
|
||||
static bool IsOpen() { return s_socket != INVALID_SOCKET; }
|
||||
static void CloseSocket() { if (IsOpen()) closesocket(s_socket); s_socket = INVALID_SOCKET; }
|
||||
#else
|
||||
static int s_socket = -1;
|
||||
static bool IsOpen() { return s_socket >= 0; }
|
||||
static void CloseSocket() { if (IsOpen()) close(s_socket); s_socket = -1; }
|
||||
#endif
|
||||
|
||||
void LoadConfig()
|
||||
{
|
||||
CloseSocket();
|
||||
s_enabled = sConfig.GetBoolDefault("MoonWell.Voice.Enable", false);
|
||||
s_secret = sConfig.GetStringDefault("MoonWell.Voice.Secret", "");
|
||||
s_address = sConfig.GetStringDefault("MoonWell.Voice.Address", "");
|
||||
s_feed = sConfig.GetStringDefault("MoonWell.Voice.Feed", "127.0.0.1:3726");
|
||||
s_interval = std::max(100, sConfig.GetIntDefault("MoonWell.Voice.FeedInterval", 250));
|
||||
s_ticketLifetime = std::max(60, sConfig.GetIntDefault("MoonWell.Voice.TicketLifetime", HOUR));
|
||||
s_sinceOpen = REOPEN_MS;
|
||||
|
||||
if (!s_enabled)
|
||||
return;
|
||||
// The relay refuses a shorter secret, so the core would sign what it never accepts.
|
||||
if (s_secret.size() < 32 || s_address.empty())
|
||||
{
|
||||
sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "MoonWell.Voice: Secret (32+ chars) and Address are required, voice disabled");
|
||||
s_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsEnabled()
|
||||
{
|
||||
return s_enabled;
|
||||
}
|
||||
|
||||
// The relay's truncated HMAC-SHA256: the label first, so a ticket never verifies as a feed
|
||||
// datagram nor the other way round.
|
||||
static void Sign(char const* label, ByteBuffer& buffer)
|
||||
{
|
||||
std::vector<uint8> message(label, label + strlen(label));
|
||||
message.insert(message.end(), buffer.contents(), buffer.contents() + buffer.size());
|
||||
uint8 mac[EVP_MAX_MD_SIZE];
|
||||
#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
|
||||
size_t macLen = 0;
|
||||
EVP_Q_mac(nullptr, "HMAC", nullptr, "SHA256", nullptr, s_secret.data(), s_secret.size(),
|
||||
message.data(), message.size(), mac, sizeof(mac), &macLen);
|
||||
#else
|
||||
unsigned int macLen = 0;
|
||||
HMAC(EVP_sha256(), s_secret.data(), int(s_secret.size()), message.data(), message.size(), mac, &macLen);
|
||||
#endif
|
||||
buffer.append(mac, MAC_LEN);
|
||||
}
|
||||
|
||||
static uint64 UnixMs()
|
||||
{
|
||||
using namespace std::chrono;
|
||||
return uint64(duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count());
|
||||
}
|
||||
|
||||
// The relay is named host:port; a host name is looked up here, so a container name works.
|
||||
static void OpenSocket()
|
||||
{
|
||||
size_t colon = s_feed.rfind(':');
|
||||
if (colon == std::string::npos)
|
||||
{
|
||||
sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "MoonWell.Voice.Feed '%s' is not host:port", s_feed.c_str());
|
||||
return;
|
||||
}
|
||||
std::string host = s_feed.substr(0, colon);
|
||||
std::string port = s_feed.substr(colon + 1);
|
||||
|
||||
addrinfo hints = {};
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
addrinfo* found = nullptr;
|
||||
if (getaddrinfo(host.c_str(), port.c_str(), &hints, &found) != 0 || !found)
|
||||
{
|
||||
sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "MoonWell.Voice: cannot resolve the relay %s", s_feed.c_str());
|
||||
return;
|
||||
}
|
||||
s_socket = socket(found->ai_family, found->ai_socktype, found->ai_protocol);
|
||||
// Connected, so a send needs no address; non-blocking, so a full buffer drops a tick and
|
||||
// never stalls the world thread.
|
||||
if (IsOpen() && connect(s_socket, found->ai_addr, int(found->ai_addrlen)) != 0)
|
||||
CloseSocket();
|
||||
freeaddrinfo(found);
|
||||
if (!IsOpen())
|
||||
return;
|
||||
#ifdef _WIN32
|
||||
u_long nonBlocking = 1;
|
||||
ioctlsocket(s_socket, FIONBIO, &nonBlocking);
|
||||
#else
|
||||
fcntl(s_socket, F_SETFL, fcntl(s_socket, F_GETFL, 0) | O_NONBLOCK);
|
||||
#endif
|
||||
sLog.Out(LOG_BASIC, LOG_LVL_MINIMAL, "MoonWell.Voice: feeding the relay at %s", s_feed.c_str());
|
||||
}
|
||||
|
||||
bool FeedDue(uint32 diff)
|
||||
{
|
||||
if (!s_enabled)
|
||||
return false;
|
||||
s_sinceFeed += diff;
|
||||
s_sinceReport += diff;
|
||||
if (s_sinceReport >= REPORT_MS)
|
||||
{
|
||||
s_sinceReport = 0;
|
||||
if (s_sent || s_failed)
|
||||
sLog.Out(LOG_BASIC, LOG_LVL_MINIMAL, "MoonWell.Voice: %u feed datagrams sent, %u failed (last error %d), %u characters in the last",
|
||||
s_sent, s_failed, s_lastError, s_lastCount);
|
||||
s_sent = 0;
|
||||
s_failed = 0;
|
||||
}
|
||||
if (!IsOpen())
|
||||
{
|
||||
s_sinceOpen += diff;
|
||||
if (s_sinceOpen < REOPEN_MS)
|
||||
return false;
|
||||
s_sinceOpen = 0;
|
||||
OpenSocket();
|
||||
if (!IsOpen())
|
||||
return false;
|
||||
}
|
||||
if (s_sinceFeed < s_interval)
|
||||
return false;
|
||||
s_sinceFeed = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void SendDatagram(uint64 sentMs, ByteBuffer const& entries, uint8 count)
|
||||
{
|
||||
ByteBuffer datagram(entries.size() + 32);
|
||||
datagram.append("MWVF", 4);
|
||||
datagram << VERSION << sentMs << count;
|
||||
datagram.append(entries.contents(), entries.size());
|
||||
Sign("moonwell-voice feed", datagram);
|
||||
if (send(s_socket, reinterpret_cast<char const*>(datagram.contents()), int(datagram.size()), 0) >= 0)
|
||||
++s_sent;
|
||||
else
|
||||
{
|
||||
++s_failed;
|
||||
#ifdef _WIN32
|
||||
s_lastError = WSAGetLastError();
|
||||
#else
|
||||
s_lastError = errno;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void SendFeed(std::vector<Player*> const& players)
|
||||
{
|
||||
if (!IsOpen())
|
||||
return;
|
||||
uint64 sentMs = UnixMs();
|
||||
s_lastCount = uint32(players.size());
|
||||
ByteBuffer entries(MAX_PER_DATAGRAM * 37);
|
||||
uint8 count = 0;
|
||||
for (Player* player : players)
|
||||
{
|
||||
Group const* group = player->GetGroup();
|
||||
entries << uint64(player->GetObjectGuid().GetRawValue());
|
||||
entries << uint8(player->GetTeam() == ALLIANCE ? 0 : 1);
|
||||
entries << uint32(player->GetMapId());
|
||||
entries << uint32(player->GetInstanceId());
|
||||
entries << player->GetPositionX() << player->GetPositionY() << player->GetPositionZ();
|
||||
entries << uint32(group ? group->GetId() : 0);
|
||||
entries << uint32(player->GetGuildId());
|
||||
if (++count == MAX_PER_DATAGRAM)
|
||||
{
|
||||
SendDatagram(sentMs, entries, count);
|
||||
entries.clear();
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
if (count)
|
||||
SendDatagram(sentMs, entries, count);
|
||||
}
|
||||
|
||||
static void SendLine(Player* player, std::string const& payload)
|
||||
{
|
||||
std::string text = "MWV\t" + payload;
|
||||
WorldPacket data;
|
||||
ChatHandler::BuildChatPacket(data, CHAT_MSG_WHISPER, text.c_str(), LANG_ADDON, CHAT_TAG_NONE,
|
||||
player->GetObjectGuid(), player->GetName());
|
||||
player->GetSession()->SendPacket(&data);
|
||||
}
|
||||
|
||||
// "T<ticket hex>\t<relay address>", or "off" when voice is disabled.
|
||||
void SendTicket(Player* player)
|
||||
{
|
||||
if (!s_enabled)
|
||||
{
|
||||
SendLine(player, "off");
|
||||
return;
|
||||
}
|
||||
ByteBuffer ticket(64);
|
||||
ticket << VERSION << uint64(player->GetObjectGuid().GetRawValue()) << uint64(UnixMs() / 1000 + s_ticketLifetime);
|
||||
Sign("moonwell-voice token", ticket);
|
||||
|
||||
static char const digits[] = "0123456789abcdef";
|
||||
std::string hex;
|
||||
for (size_t i = 0; i < ticket.size(); ++i)
|
||||
{
|
||||
hex += digits[ticket.contents()[i] >> 4];
|
||||
hex += digits[ticket.contents()[i] & 0xF];
|
||||
}
|
||||
SendLine(player, "T" + hex + "\t" + s_address);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef MANGOS_MOONWELL_VOICE_H
|
||||
#define MANGOS_MOONWELL_VOICE_H
|
||||
|
||||
#include "Common.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
class Player;
|
||||
|
||||
// MoonWell voice chat. Voice goes to a separate relay (moonwell-voice-relay in the MoonWell
|
||||
// client repo), never through the world socket; the core only vouches for who a client is and
|
||||
// where every character stands:
|
||||
// - `.mwvoice` answers the player with a signed ticket and the relay's address, as an addon
|
||||
// line (LANG_ADDON, prefix MWV), so the client can open a voice session as that character;
|
||||
// - every MoonWell.Voice.FeedInterval ms the world thread sends the relay each character in the
|
||||
// world: faction, map, instance, position, group and guild. The relay decides who hears whom
|
||||
// from that alone, so no client can claim a place, group or guild that is not its own.
|
||||
// Both are signed with MoonWell.Voice.Secret, which the relay shares. The byte layouts are the
|
||||
// relay's `token` and `feed` modules.
|
||||
namespace MoonWellVoice
|
||||
{
|
||||
void LoadConfig();
|
||||
|
||||
bool IsEnabled();
|
||||
|
||||
// Counts the world's diff and says whether a feed is due; the caller then gathers the players
|
||||
// under the lock that keeps async packets out, and calls SendFeed.
|
||||
bool FeedDue(uint32 diff);
|
||||
|
||||
void SendFeed(std::vector<Player*> const& players);
|
||||
|
||||
void SendTicket(Player* player);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -9590,6 +9590,12 @@ uint8 ObjectMgr::CheckPlayerName(std::string const& name, bool create)
|
||||
if (wname.size() < minName)
|
||||
return CHAR_NAME_TOO_SHORT;
|
||||
|
||||
for (size_t i = 2; i < wname.size(); ++i)
|
||||
{
|
||||
if (wname[i] == wname[i - 1] && wname[i] == wname[i - 2])
|
||||
return CHAR_NAME_THREE_CONSECUTIVE;
|
||||
}
|
||||
|
||||
uint32 strictMask = sWorld.getConfig(CONFIG_UINT32_STRICT_PLAYER_NAMES);
|
||||
if (!isValidString(wname, strictMask, false, create))
|
||||
return CHAR_NAME_MIXED_LANGUAGES;
|
||||
|
||||
@@ -22770,7 +22770,7 @@ void Log::Player(WorldSession const* session, LogType logType, char const* subTy
|
||||
// Player logs should never go to the console
|
||||
}
|
||||
|
||||
void Log:: Player(uint32 accountId, LogType logType, LogLevel logLevel, char const* format, ...)
|
||||
void Log::Player(uint32 accountId, LogType logType, LogLevel logLevel, char const* format, ...)
|
||||
{
|
||||
if (logType >= LOG_TYPE_MAX || logType < 0)
|
||||
return;
|
||||
|
||||
@@ -2953,7 +2953,7 @@ void Spell::EffectTeleUnitsFaceCaster(SpellEffectIndex effIdx)
|
||||
m_caster->GetClosePoint(fx, fy, fz, unitTarget->GetObjectBoundingRadius(), dis);
|
||||
}
|
||||
|
||||
unitTarget->NearTeleportTo(fx, fy, fz, -m_caster->GetOrientation(), TELE_TO_NOT_LEAVE_COMBAT | TELE_TO_NOT_UNSUMMON_PET | (unitTarget == m_caster ? TELE_TO_SPELL : 0));
|
||||
unitTarget->NearTeleportTo(fx, fy, fz, m_caster->GetAngle(fx, fy) - M_PI_F, TELE_TO_NOT_LEAVE_COMBAT | TELE_TO_NOT_UNSUMMON_PET | (unitTarget == m_caster ? TELE_TO_SPELL : 0));
|
||||
}
|
||||
|
||||
void Spell::EffectLearnSkill(SpellEffectIndex effIdx)
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
#include "Chat.h"
|
||||
#include "DBCStores.h"
|
||||
#include "MassMailMgr.h"
|
||||
#include "MoonWellVoice.h"
|
||||
#include "LootMgr.h"
|
||||
#include "ItemEnchantmentMgr.h"
|
||||
#include "MapManager.h"
|
||||
@@ -502,6 +503,7 @@ void World::LoadConfigSettings(bool reload)
|
||||
setConfigPos(CONFIG_FLOAT_RATE_DROP_ITEM_LEGENDARY, "Rate.Drop.Item.Legendary", 1.0f);
|
||||
setConfigPos(CONFIG_FLOAT_RATE_DROP_ITEM_ARTIFACT, "Rate.Drop.Item.Artifact", 1.0f);
|
||||
setConfigPos(CONFIG_FLOAT_RATE_DROP_ITEM_REFERENCED, "Rate.Drop.Item.Referenced", 1.0f);
|
||||
setConfigPos(CONFIG_FLOAT_RATE_DROP_ITEM_QUEST, "Rate.Drop.Item.Quest", 1.0f);
|
||||
setConfigPos(CONFIG_FLOAT_RATE_DROP_MONEY, "Rate.Drop.Money", 1.0f);
|
||||
setConfig(CONFIG_FLOAT_RATE_XP_KILL, "Rate.XP.Kill", 1.0f);
|
||||
setConfig(CONFIG_FLOAT_RATE_XP_KILL_ELITE, "Rate.XP.Kill.Elite", 1.0f);
|
||||
@@ -673,6 +675,8 @@ void World::LoadConfigSettings(bool reload)
|
||||
setConfigMinMax(CONFIG_FLOAT_DUNGEON_SCALING_DAMAGE_4, "DungeonScaling.Damage.4", 0.90f, 0.05f, 1.0f);
|
||||
setConfigMinMax(CONFIG_FLOAT_DUNGEON_FULL_GROUP_RARE_GEAR_CHANCE, "DungeonScaling.FullGroup.RareGearChance", 1.2f, 1.0f, 10.0f);
|
||||
|
||||
MoonWellVoice::LoadConfig();
|
||||
|
||||
setConfig(CONFIG_UINT32_MAX_PRIMARY_TRADE_SKILL, "MaxPrimaryTradeSkill", 2);
|
||||
setConfigMinMax(CONFIG_UINT32_MIN_PETITION_SIGNS, "MinPetitionSigns", 9, 0, 9);
|
||||
|
||||
@@ -2145,6 +2149,24 @@ void World::Update(uint32 diff)
|
||||
// Update ban list if necessary
|
||||
sAccountMgr.Update(diff);
|
||||
|
||||
// MoonWell voice: every character in the world to the relay. After the map update, and under
|
||||
// the lock that keeps async packets out, so no group or guild changes while it is read.
|
||||
if (MoonWellVoice::FeedDue(diff))
|
||||
{
|
||||
m_canProcessAsyncPackets = false;
|
||||
std::vector<Player*> players;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_asyncPacketsMutex);
|
||||
players.reserve(m_sessions.size());
|
||||
for (auto const& itr : m_sessions)
|
||||
if (Player* player = itr.second->GetPlayer())
|
||||
if (player->IsInWorld())
|
||||
players.push_back(player);
|
||||
MoonWellVoice::SendFeed(players);
|
||||
}
|
||||
m_canProcessAsyncPackets = true;
|
||||
}
|
||||
|
||||
{
|
||||
m_canProcessAsyncPackets = false;
|
||||
std::lock_guard<std::mutex> lock(m_asyncPacketsMutex);
|
||||
|
||||
@@ -408,6 +408,7 @@ enum eConfigFloatValues
|
||||
CONFIG_FLOAT_RATE_DROP_ITEM_LEGENDARY,
|
||||
CONFIG_FLOAT_RATE_DROP_ITEM_ARTIFACT,
|
||||
CONFIG_FLOAT_RATE_DROP_ITEM_REFERENCED,
|
||||
CONFIG_FLOAT_RATE_DROP_ITEM_QUEST,
|
||||
CONFIG_FLOAT_RATE_DROP_MONEY,
|
||||
CONFIG_FLOAT_RATE_XP_KILL,
|
||||
CONFIG_FLOAT_RATE_XP_KILL_ELITE,
|
||||
|
||||
@@ -2626,8 +2626,9 @@ Visibility.ForceActiveObjects = 1
|
||||
# Rate.Drop.Item.Legendary
|
||||
# Rate.Drop.Item.Artifact
|
||||
# Rate.Drop.Item.Referenced
|
||||
# Rate.Drop.Item.Quest
|
||||
# Rate.Drop.Money
|
||||
# Drop rates (items by quality and money)
|
||||
# Drop rates (items by quality, quest items in place of their quality, and money)
|
||||
# Default: 1
|
||||
#
|
||||
# Rate.XP.Kill
|
||||
@@ -2805,6 +2806,7 @@ Rate.Drop.Item.Epic = 1
|
||||
Rate.Drop.Item.Legendary = 1
|
||||
Rate.Drop.Item.Artifact = 1
|
||||
Rate.Drop.Item.Referenced = 1
|
||||
Rate.Drop.Item.Quest = 1
|
||||
Rate.Drop.Money = 1
|
||||
Rate.XP.Kill = 1
|
||||
Rate.XP.Kill.Elite = 1
|
||||
@@ -2958,6 +2960,42 @@ Alterac.InitMaxPlayers = 0
|
||||
OutdoorPvP.SI.Enable = 1
|
||||
OutdoorPvP.EP.Enable = 1
|
||||
|
||||
###################################################################################################################
|
||||
# MOONWELL VOICE
|
||||
#
|
||||
# MoonWell.Voice.Enable
|
||||
# Vouch for MoonWell voice chat: answer .mwvoice with a signed ticket and feed the voice relay
|
||||
# (moonwell-voice-relay) every character in the world. Voice itself never goes through mangosd.
|
||||
# Default: 0 (disable)
|
||||
#
|
||||
# MoonWell.Voice.Secret
|
||||
# Signs tickets and the feed; must equal the relay's MOONWELL_VOICE_SECRET, 32 chars or more.
|
||||
#
|
||||
# MoonWell.Voice.Address
|
||||
# The relay's public host:port, as sent to clients.
|
||||
#
|
||||
# MoonWell.Voice.Feed
|
||||
# Where the feed goes: the relay's --feed address. Keep it off the public interface.
|
||||
# Default: "127.0.0.1:3726"
|
||||
#
|
||||
# MoonWell.Voice.FeedInterval
|
||||
# Milliseconds between feeds (100 or more). A character kicked from a group or guild stops
|
||||
# hearing it within one interval.
|
||||
# Default: 250
|
||||
#
|
||||
# MoonWell.Voice.TicketLifetime
|
||||
# Seconds a ticket opens a voice session for (60 or more); the client asks again before then.
|
||||
# Default: 3600
|
||||
#
|
||||
###################################################################################################################
|
||||
|
||||
MoonWell.Voice.Enable = 0
|
||||
MoonWell.Voice.Secret = ""
|
||||
MoonWell.Voice.Address = ""
|
||||
MoonWell.Voice.Feed = "127.0.0.1:3726"
|
||||
MoonWell.Voice.FeedInterval = 250
|
||||
MoonWell.Voice.TicketLifetime = 3600
|
||||
|
||||
###################################################################################################################
|
||||
# DUNGEON SCALING
|
||||
#
|
||||
|
||||
+37
-20
@@ -171,7 +171,7 @@ void AuthSocket::DoRecvIncomingData()
|
||||
|
||||
std::shared_ptr<ByteBuffer> AuthSocket::GenerateLogonProofResponse(Crypto::Hash::SHA1::Digest const& shaDigest)
|
||||
{
|
||||
std::shared_ptr<ByteBuffer> pkt(new ByteBuffer());
|
||||
auto pkt = std::make_shared<ByteBuffer>();
|
||||
|
||||
if (m_build < 6299) // before version 2.0.3 (exclusive)
|
||||
{
|
||||
@@ -587,7 +587,7 @@ void AuthSocket::_HandleLogonProof()
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<PINData> pinData(new PINData());
|
||||
auto pinData = std::make_shared<PINData>();
|
||||
self->m_socket.Read((char*) pinData.get(), sizeof(PINData), [self, lp, pinData](IO::NetworkError const& error, size_t)
|
||||
{
|
||||
self->_HandleLogonProof__PostRecv(lp, pinData);
|
||||
@@ -620,7 +620,7 @@ void AuthSocket::_HandleLogonProof__PostRecv_HandleInvalidVersion(std::shared_pt
|
||||
if (m_pendingPatchFile == nullptr)
|
||||
{
|
||||
// no patch found
|
||||
std::shared_ptr<ByteBuffer> pkt(new ByteBuffer());
|
||||
auto pkt = std::make_shared<ByteBuffer>();
|
||||
*pkt << (uint8) CMD_AUTH_LOGON_CHALLENGE;
|
||||
*pkt << (uint8) 0x00;
|
||||
*pkt << (uint8) WOW_FAIL_VERSION_INVALID;
|
||||
@@ -643,7 +643,7 @@ void AuthSocket::_HandleLogonProof__PostRecv_HandleInvalidVersion(std::shared_pt
|
||||
std::string wowClientPathType = "Patch"; // Must be patch "Patch"
|
||||
MANGOS_ASSERT(wowClientPathType.size() <= 255); // Filename must fit inside a byte
|
||||
|
||||
std::shared_ptr<ByteBuffer> pkt(new ByteBuffer());
|
||||
auto pkt = std::make_shared<ByteBuffer>();
|
||||
|
||||
// packet 1
|
||||
*pkt << (uint8) CMD_AUTH_LOGON_PROOF;
|
||||
@@ -714,7 +714,7 @@ void AuthSocket::_HandleLogonProof__PostRecv(std::shared_ptr<sAuthLogonProof_C c
|
||||
pinResult = VerifyPinData(std::stoi(m_securityInfo), *pinData);
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "[AuthChallenge] Account '%s' failed to parse FIXED_PIN from security info: %s", m_login.c_str(), e.what());
|
||||
pinResult = false;
|
||||
@@ -723,15 +723,31 @@ void AuthSocket::_HandleLogonProof__PostRecv(std::shared_ptr<sAuthLogonProof_C c
|
||||
}
|
||||
else if ((m_lockFlags & TOTP) == TOTP)
|
||||
{
|
||||
for (int i = -2; i != 2; ++i)
|
||||
try
|
||||
{
|
||||
auto pin = GenerateTotpPin(m_securityInfo, i);
|
||||
if (m_securityInfo.empty())
|
||||
{
|
||||
sLog.Out(LOG_BASIC, LOG_LVL_DEBUG, "[AuthChallenge] Account '%s' has empty security info for TOTP", m_login.c_str());
|
||||
pinResult = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = -2; i != 2; ++i)
|
||||
{
|
||||
auto pin = GenerateTotpPin(m_securityInfo, i);
|
||||
|
||||
if (pin == uint32(-1))
|
||||
break;
|
||||
if (pin == uint32(-1))
|
||||
break;
|
||||
|
||||
if ((pinResult = VerifyPinData(pin, *pinData)))
|
||||
break;
|
||||
if ((pinResult = VerifyPinData(pin, *pinData)))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "[AuthChallenge] Account '%s' failed to parse TOTP from security info: %s", m_login.c_str(), e.what());
|
||||
pinResult = false;
|
||||
}
|
||||
}
|
||||
else if (m_geoUnlockPIN)
|
||||
@@ -746,13 +762,14 @@ void AuthSocket::_HandleLogonProof__PostRecv(std::shared_ptr<sAuthLogonProof_C c
|
||||
}
|
||||
|
||||
// Check if SRP6 results match (password is correct), else send an error
|
||||
if (!srp.Proof(lp->M1, 20) && pinResult)
|
||||
bool const passwordResult = !srp.Proof(lp->M1, 20);
|
||||
if (passwordResult && pinResult)
|
||||
{
|
||||
if (!VerifyVersion(lp->A, sizeof(lp->A), lp->crc_hash, false))
|
||||
{
|
||||
sLog.Out(LOG_BASIC, LOG_LVL_BASIC, "[AuthChallenge] Account %s tried to login with modified client!", m_login.c_str());
|
||||
|
||||
std::shared_ptr<ByteBuffer> pkt(new ByteBuffer());
|
||||
auto pkt = std::make_shared<ByteBuffer>();
|
||||
*pkt << (uint8) CMD_AUTH_LOGON_PROOF;
|
||||
*pkt << (uint8) WOW_FAIL_VERSION_INVALID;
|
||||
m_socket.Write(std::move(pkt), [self = shared_from_this()](IO::NetworkError const& error)
|
||||
@@ -781,7 +798,7 @@ void AuthSocket::_HandleLogonProof__PostRecv(std::shared_ptr<sAuthLogonProof_C c
|
||||
{
|
||||
sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "Unable to write geolock PIN for %s - account has not been locked", m_safelogin.c_str());
|
||||
|
||||
std::shared_ptr<ByteBuffer> pkt(new ByteBuffer());
|
||||
auto pkt = std::make_shared<ByteBuffer>();
|
||||
*pkt << (uint8) CMD_AUTH_LOGON_PROOF;
|
||||
*pkt << (uint8) WOW_FAIL_DB_BUSY;
|
||||
m_socket.Write(std::move(pkt), [self = shared_from_this()](IO::NetworkError const& error)
|
||||
@@ -815,7 +832,7 @@ void AuthSocket::_HandleLogonProof__PostRecv(std::shared_ptr<sAuthLogonProof_C c
|
||||
}
|
||||
#endif
|
||||
|
||||
std::shared_ptr<ByteBuffer> pkt(new ByteBuffer());
|
||||
auto pkt = std::make_shared<ByteBuffer>();
|
||||
*pkt << (uint8) CMD_AUTH_LOGON_PROOF;
|
||||
*pkt << (uint8) WOW_FAIL_PARENTCONTROL;
|
||||
m_socket.Write(std::move(pkt), [self = shared_from_this()](IO::NetworkError const& error)
|
||||
@@ -888,12 +905,12 @@ void AuthSocket::_HandleLogonProof__PostRecv(std::shared_ptr<sAuthLogonProof_C c
|
||||
else
|
||||
{
|
||||
// We are here because the password was incorrect
|
||||
sLog.Out(LOG_BASIC, LOG_LVL_BASIC, "[AuthChallenge] Account '%s' using IP '%s' tried to login with wrong password!", m_login.c_str (), GetRemoteIpString().c_str());
|
||||
sLog.Out(LOG_BASIC, LOG_LVL_BASIC, "[AuthChallenge] Account '%s' using IP '%s' tried to login with wrong %s!", m_login.c_str (), GetRemoteIpString().c_str(), (passwordResult ? "pin" : "password"));
|
||||
|
||||
// Record the failure for the in-memory brute-force guard.
|
||||
RecordWrongPasswordAttempt(GetRemoteIpString());
|
||||
|
||||
std::shared_ptr<ByteBuffer> pkt(new ByteBuffer());
|
||||
auto pkt = std::make_shared<ByteBuffer>();
|
||||
*pkt << (uint8) CMD_AUTH_LOGON_PROOF;
|
||||
*pkt << (uint8) WOW_FAIL_UNKNOWN_ACCOUNT;
|
||||
if (m_build > 6005) // > 1.12.2
|
||||
@@ -953,7 +970,7 @@ void AuthSocket::_HandleReconnectProof()
|
||||
m_status = STATUS_INVALID;
|
||||
|
||||
// Read the packet
|
||||
std::shared_ptr<AUTH_RECONNECT_PROOF_C> lp(new AUTH_RECONNECT_PROOF_C());
|
||||
auto lp = std::make_shared<AUTH_RECONNECT_PROOF_C>();
|
||||
m_socket.Read((char*) lp.get(), sizeof(AUTH_RECONNECT_PROOF_C), [self = shared_from_this(), lp](IO::NetworkError const& error, size_t)
|
||||
{
|
||||
if (error)
|
||||
@@ -1056,7 +1073,7 @@ void AuthSocket::_HandleRealmList()
|
||||
ByteBuffer realmlistBuffer;
|
||||
self->LoadRealmlistAndWriteIntoBuffer(realmlistBuffer);
|
||||
|
||||
std::shared_ptr<ByteBuffer> pkt(new ByteBuffer());
|
||||
auto pkt = std::make_shared<ByteBuffer>();
|
||||
*pkt << (uint8) CMD_REALM_LIST;
|
||||
*pkt << (uint16)realmlistBuffer.size();
|
||||
pkt->append(realmlistBuffer);
|
||||
@@ -1370,7 +1387,7 @@ void AuthSocket::InitAndHandOverControlToPatchHandler()
|
||||
{
|
||||
MANGOS_ASSERT(m_pendingPatchFile);
|
||||
|
||||
std::shared_ptr<XFER_DATA_CHUNK> rawChunk(new XFER_DATA_CHUNK());
|
||||
auto rawChunk = std::make_shared<XFER_DATA_CHUNK>();
|
||||
rawChunk->cmd = CMD_XFER_DATA;
|
||||
|
||||
RepeatInternalXferLoop(rawChunk);
|
||||
|
||||
@@ -952,7 +952,7 @@ bool GossipHello_ProfessionNPC(Player* player, Creature* creature)
|
||||
|
||||
void CompleteLearnProfession(Player *pPlayer, Creature *pCreature, SkillType skill)
|
||||
{
|
||||
if (pPlayer->GetFreePrimaryProfessionPoints() == 0 && !(skill == SKILL_COOKING || skill == SKILL_FIRST_AID))
|
||||
if (pPlayer->GetFreePrimaryProfessionPoints() == 0 && !(skill == SKILL_COOKING || skill == SKILL_FIRST_AID || skill == SKILL_FISHING))
|
||||
{
|
||||
pPlayer->GetSession()->SendNotification("You already know two primary professions.");
|
||||
}
|
||||
|
||||
@@ -859,7 +859,7 @@ struct npc_rabid_thistle_bearAI : public FollowerAI
|
||||
|
||||
void StartFollowing(Player* pPlayer)
|
||||
{
|
||||
Captured_Timer = 300000;
|
||||
Captured_Timer = 500000;
|
||||
StartFollow(pPlayer);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user