diff --git a/sql/migrations/20180824175920_logon.sql b/sql/migrations/20180824175920_logon.sql new file mode 100644 index 00000000..9cc50476 --- /dev/null +++ b/sql/migrations/20180824175920_logon.sql @@ -0,0 +1,45 @@ +DROP PROCEDURE IF EXISTS add_migration; +delimiter ?? +CREATE PROCEDURE `add_migration`() +BEGIN +DECLARE v INT DEFAULT 1; +SET v = (SELECT COUNT(*) FROM `migrations` WHERE `id`='20180824175920'); +IF v=0 THEN +INSERT INTO `migrations` VALUES ('20180824175920'); +-- Add your query below. + +ALTER TABLE `account` ADD COLUMN `invite_id` INT DEFAULT NULL, +ADD UNIQUE INDEX `invite_id_UNIQUE` (`invite_id` ASC); + +ALTER TABLE `account` +ADD INDEX `invite_id_fk_idx` (`invite_id` ASC); +ALTER TABLE `account` +ADD CONSTRAINT `invite_id_fk` + FOREIGN KEY (`invite_id`) + REFERENCES `invites` (`id`) + ON DELETE SET NULL + ON UPDATE CASCADE; + +CREATE TABLE `invites` ( + `id` int(11) NOT NULL, + `generator` int(11) unsigned NOT NULL, + `code` varchar(45) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `code_UNIQUE` (`code`), + KEY `generator_fk_idx` (`generator`), + CONSTRAINT `generator_fk` FOREIGN KEY (`generator`) REFERENCES `account` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO `mangos_string` (`entry`, `content_default`) VALUES ('816', 'Your account is in restricted mode and cannot use the auction house until you add an invite code to your account or reach level %u. Please see %s for details.'); +INSERT INTO `mangos_string` (`entry`, `content_default`) VALUES ('817', 'Your account is in restricted mode and cannot send mail until you add an invite code to your account or reach level %u. Please see %s for details.'); +INSERT INTO `mangos_string` (`entry`, `content_default`) VALUES ('818', 'Your account is in restricted mode and cannot invite players that do not have you on their friends list until you add an invite code to your account or reach level %u. Please see %s for details.'); +INSERT INTO `mangos_string` (`entry`, `content_default`) VALUES ('819', 'Your account is in restricted mode and cannot whisper players that do not have you on their friends list until you add an invite code to your account or reach level %u. Please see %s for details.'); +INSERT INTO `mangos_string` (`entry`, `content_default`) VALUES ('820', 'Your account is in restricted mode and may only receive but not send items through trading until you add an invite code to your account or reach level %u. Please see %s for details.'); + + +-- End of migration. +END IF; +END?? +delimiter ; +CALL add_migration(); +DROP PROCEDURE IF EXISTS add_migration; diff --git a/src/game/Handlers/AuctionHouseHandler.cpp b/src/game/Handlers/AuctionHouseHandler.cpp index 143ce175..6ddc5c19 100644 --- a/src/game/Handlers/AuctionHouseHandler.cpp +++ b/src/game/Handlers/AuctionHouseHandler.cpp @@ -33,6 +33,7 @@ #include "Util.h" #include "Chat.h" #include "Anticheat.h" +#include "Language.h" // please DO NOT use iterator++, because it is slower than ++iterator!!! // post-incrementation is always slower than pre-incrementation ! @@ -274,6 +275,12 @@ void WorldSession::HandleAuctionSellItem(WorldPacket & recv_data) Player *pl = GetPlayer(); + if (IsAccountRestricted()) + { + SendRestrictedHelp(LANG_INV_AUCTION_LIST_RESTRICTED); + return; + } + AuctionHouseEntry const* auctionHouseEntry = GetCheckedAuctionHouseForAuctioneer(auctioneerGuid); if (!auctionHouseEntry) return; @@ -412,6 +419,12 @@ void WorldSession::HandleAuctionPlaceBid(WorldPacket & recv_data) { DEBUG_LOG("WORLD: HandleAuctionPlaceBid"); + if (IsAccountRestricted()) + { + SendRestrictedHelp(LANG_INV_AUCTION_LIST_RESTRICTED); + return; + } + ObjectGuid auctioneerGuid; uint32 auctionId; uint32 price; diff --git a/src/game/Handlers/ChatHandler.cpp b/src/game/Handlers/ChatHandler.cpp index fe18a73d..2d74e103 100644 --- a/src/game/Handlers/ChatHandler.cpp +++ b/src/game/Handlers/ChatHandler.cpp @@ -40,6 +40,7 @@ #include "CellImpl.h" #include "Anticheat.h" #include "AccountMgr.h" +#include "SocialMgr.h" bool WorldSession::processChatmessageFurtherAfterSecurityChecks(std::string& msg, uint32 lang) { @@ -404,9 +405,25 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket & recv_data) SendWrongFactionNotice(); return; } - if (/*player->GetZoneId() != masterPlr->GetZoneId() && */masterPlr->getLevel() < sWorld.getConfig(CONFIG_UINT32_WHISP_DIFF_ZONE_MIN_LEVEL)) + + const auto whisperLevelReq = sWorld.getConfig(CONFIG_UINT32_WHISP_MIN_LEVEL); + const auto maxLevel = masterPlr->GetSession()->GetAccountMaxLevel(); + const auto social = player->GetSession()->GetPlayer()->GetSocial(); // (╯°□°)╯︵ ┻━┻ + + if (maxLevel < whisperLevelReq && !social->HasFriend(masterPlr->GetObjectGuid())) { - ChatHandler(this).SendSysMessage("You cannot whisper yet."); + if (IsAccountRestricted()) // only applies if below the old whisper limit + { + SendRestrictedHelp(LANG_INV_WHISPER_RESTRICTED); + } + else + { + ChatHandler(this).PSendSysMessage( + "You cannot whisper players that do not have you on their friends list until you " + "have a level %u or higher character on your account.", whisperLevelReq + ); + } + return; } } diff --git a/src/game/Handlers/GroupHandler.cpp b/src/game/Handlers/GroupHandler.cpp index 6ce78c09..a4bce683 100644 --- a/src/game/Handlers/GroupHandler.cpp +++ b/src/game/Handlers/GroupHandler.cpp @@ -31,6 +31,7 @@ #include "Group.h" #include "SocialMgr.h" #include "Util.h" +#include "Language.h" /* differeces from off: -you can uninvite yourself - is is useful @@ -91,6 +92,15 @@ void WorldSession::HandleGroupInviteOpcode(WorldPacket & recv_data) return; } + if (IsAccountRestricted()) + { + if (!player->GetSocial()->HasFriend(GetPlayer()->GetObjectGuid())) + { + SendRestrictedHelp(LANG_INV_PARTY_INVITE_RESTRICTED); + return; + } + } + Group *group = GetPlayer()->GetGroup(); if (group && group->isBGGroup()) group = GetPlayer()->GetOriginalGroup(); diff --git a/src/game/Handlers/MailHandler.cpp b/src/game/Handlers/MailHandler.cpp index bb385828..74944652 100644 --- a/src/game/Handlers/MailHandler.cpp +++ b/src/game/Handlers/MailHandler.cpp @@ -108,11 +108,19 @@ class WorldSession::AsyncMailSendRequest */ void WorldSession::HandleSendMail(WorldPacket & recv_data) { + if (IsAccountRestricted()) + { + SendRestrictedHelp(LANG_INV_MAIL_SEND_RESTRICTED); + return; + } + ObjectGuid mailboxGuid; uint64 unk3; uint32 unk1, unk2; uint8 unk4; + recv_data >> mailboxGuid; + if (!CheckMailBox(mailboxGuid)) return; diff --git a/src/game/Handlers/TradeHandler.cpp b/src/game/Handlers/TradeHandler.cpp index e8f3d138..3ba7208c 100644 --- a/src/game/Handlers/TradeHandler.cpp +++ b/src/game/Handlers/TradeHandler.cpp @@ -256,6 +256,29 @@ void WorldSession::HandleAcceptTradeOpcode(WorldPacket& recvPacket) TradeData* my_trade = _player->m_trade; if (!my_trade) return; + + if (IsAccountRestricted()) + { + if (my_trade->GetMoney()) + { + SendRestrictedHelp(LANG_INV_TRADE_SEND_RESTRICTED); + SendTradeStatus(TRADE_STATUS_TRIAL_ACCOUNT); + my_trade->SetAccepted(false, false); + return; + } + + for (int i = 0; i < TRADE_SLOT_TRADED_COUNT; ++i) + { + if (my_trade->GetItem(TradeSlots(i))) + { + SendRestrictedHelp(LANG_INV_TRADE_SEND_RESTRICTED); + SendTradeStatus(TRADE_STATUS_TRIAL_ACCOUNT); + my_trade->SetAccepted(false, false); + return; + } + } + } + double lastModificationTimeInMS = difftime(time(NULL), my_trade->GetLastModificationTime()) * 1000; if (lastModificationTimeInMS < my_trade->GetScamPreventionDelay()) // if we are not outside the delay period since last modification { diff --git a/src/game/Language.h b/src/game/Language.h index 0ef18b73..4dad3189 100644 --- a/src/game/Language.h +++ b/src/game/Language.h @@ -822,7 +822,14 @@ enum MangosStrings LANG_GUILD_VETERAN = 813, LANG_GUILD_MEMBER = 814, LANG_GUILD_INITIATE = 815, - // Room for in-game strings 816-899 not used + + // invite restriction strings + LANG_INV_AUCTION_LIST_RESTRICTED = 816, + LANG_INV_MAIL_SEND_RESTRICTED = 817, + LANG_INV_PARTY_INVITE_RESTRICTED = 818, + LANG_INV_WHISPER_RESTRICTED = 819, + LANG_INV_TRADE_SEND_RESTRICTED = 820, + // Room for in-game strings 821-899 not used // More Modify Options LANG_YOU_CHANGE_STR = 900, diff --git a/src/game/Objects/Player.cpp b/src/game/Objects/Player.cpp index 9488fe7d..d6b5087d 100644 --- a/src/game/Objects/Player.cpp +++ b/src/game/Objects/Player.cpp @@ -2889,6 +2889,12 @@ void Player::GiveLevel(uint32 level) if (m_session->ShouldBeBanned(getLevel())) sWorld.BanAccount(BAN_ACCOUNT, m_session->GetUsername(), 0, m_session->GetScheduleBanReason(), ""); + + if(level > GetSession()->GetAccountMaxLevel()) + { + GetSession()->SetAccountMaxLevel(level); + } + sAnticheatLib->OnPlayerLevelUp(this); } diff --git a/src/game/Protocol/WorldSocket.cpp b/src/game/Protocol/WorldSocket.cpp index 184f53ee..7ff17483 100644 --- a/src/game/Protocol/WorldSocket.cpp +++ b/src/game/Protocol/WorldSocket.cpp @@ -163,9 +163,9 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) LoginDatabase.escape_string(safe_account); // No SQL injection, username escaped. - QueryResult *result = LoginDatabase.PQuery("SELECT a.id, aa.gmLevel, a.sessionkey, a.last_ip, a.locked, a.v, a.s, a.mutetime, a.locale, a.os, a.flags, " - "ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate FROM account a LEFT JOIN account_access aa ON a.id = aa.id AND aa.RealmID IN (-1, %u) " - "LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 WHERE a.username = '%s' ORDER BY aa.RealmID DESC LIMIT 1", realmID, safe_account.c_str()); + auto result = std::unique_ptr(LoginDatabase.PQuery("SELECT a.id, aa.gmLevel, a.sessionkey, a.last_ip, a.locked, a.v, a.s, a.mutetime, a.locale, a.os, a.flags, " + "ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, a.invite_id FROM account a LEFT JOIN account_access aa ON a.id = aa.id AND aa.RealmID IN (-1, %u) " + "LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 WHERE a.username = '%s' ORDER BY aa.RealmID DESC LIMIT 1", realmID, safe_account.c_str())); // Stop if the account is not found if (!result) @@ -205,8 +205,6 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) packet.Initialize(SMSG_AUTH_RESPONSE, 1); packet << uint8(AUTH_FAILED); SendPacket(packet); - - delete result; BASIC_LOG("WorldSocket::HandleAuthSession: Sent Auth Response (Account IP differs)."); return -1; } @@ -221,7 +219,6 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) if (K.AsByteArray().empty()) { - delete result; return -1; } @@ -233,9 +230,8 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) os = fields[9].GetString(); uint32 accFlags = fields[10].GetUInt32(); bool isBanned = fields[11].GetBool(); - delete result; + uint32 inviteID = fields[12].GetUInt32(); - if (isBanned || sAccountMgr.IsIPBanned(GetRemoteAddress())) { packet.Initialize(SMSG_AUTH_RESPONSE, 1); @@ -317,6 +313,7 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) m_Session->SetUsername(account); m_Session->SetGameBuild(BuiltNumberClient); m_Session->SetAccountFlags(accFlags); + m_Session->SetInviteID(inviteID); m_Session->SetOS(clientOs); m_Session->LoadTutorialsData(); m_Session->InitWarden(&K); diff --git a/src/game/World.cpp b/src/game/World.cpp index b94f31ef..0e516e68 100644 --- a/src/game/World.cpp +++ b/src/game/World.cpp @@ -732,6 +732,7 @@ void World::LoadConfigSettings(bool reload) setConfig(CONFIG_BOOL_OUTDOORPVP_SI_ENABLE, "OutdoorPvP.SI.Enable", true); setConfig(CONFIG_UINT32_ANTIFLOOD_SANCTION, "Antiflood.Sanction", CHEAT_ACTION_KICK); + setConfig(CONFIG_BOOL_ENABLE_INVITES, "Invites.Enable", false); m_relocation_ai_notify_delay = sConfig.GetIntDefault("Visibility.AIRelocationNotifyDelay", 1000u); m_relocation_lower_limit_sq = pow(sConfig.GetFloatDefault("Visibility.RelocationLowerLimit", 10), 2); @@ -971,7 +972,7 @@ void World::LoadNostalriusConfig(bool reload) setConfig(CONFIG_UINT32_SAY_MIN_LEVEL, "SayMinLevel", 0); setConfig(CONFIG_UINT32_YELL_MIN_LEVEL, "YellMinLevel", 0); setConfig(CONFIG_UINT32_SAY_EMOTE_MIN_LEVEL, "SayEmoteMinLevel", 0); - setConfig(CONFIG_UINT32_WHISP_DIFF_ZONE_MIN_LEVEL, "WhisperDiffZone.MinLevel", 0); + setConfig(CONFIG_UINT32_WHISP_MIN_LEVEL, "Whisper.MinLevel", 0); setConfig(CONFIG_UINT32_YELLRANGE_LINEARSCALE_MAXLEVEL, "YellRange.LinearScale.MaxLevel", 0); setConfig(CONFIG_UINT32_YELLRANGE_QUADRATICSCALE_MAXLEVEL, "YellRange.QuadraticScale.MaxLevel", 0); setConfig(CONFIG_UINT32_YELLRANGE_MIN, "YellRange.Min", 0); @@ -1033,6 +1034,7 @@ void World::LoadNostalriusConfig(bool reload) setConfig(CONFIG_UINT32_WAR_EFFORT_AUTOCOMPLETE_PERIOD, "WarEffortResourceCompletePeriod", 86400); setConfig(CONFIG_UINT32_ACCOUNT_CONCURRENT_AUCTION_LIMIT, "Auction.AccountConcurrentLimit", 0); + setConfig(CONFIG_UINT32_INVITE_RESTRICT_CAP, "Invites.RestrictionCap", 0); if (getConfig(CONFIG_BOOL_ALLOW_TWO_SIDE_INTERACTION_CHAT)) setConfig(CONFIG_BOOL_GM_JOIN_OPPOSITE_FACTION_CHANNELS, false); diff --git a/src/game/World.h b/src/game/World.h index 9961c8a0..4d4e7927 100644 --- a/src/game/World.h +++ b/src/game/World.h @@ -115,7 +115,7 @@ enum eConfigUInt32Values CONFIG_UINT32_AV_INITIAL_MAX_PLAYERS, CONFIG_UINT32_INACTIVE_PLAYERS_SKIP_UPDATES, CONFIG_UINT32_ITEM_INSTANTSAVE_QUALITY, - CONFIG_UINT32_WHISP_DIFF_ZONE_MIN_LEVEL, + CONFIG_UINT32_WHISP_MIN_LEVEL, CONFIG_UINT32_CHANNEL_INVITE_MIN_LEVEL, CONFIG_UINT32_WORLD_CHAN_MIN_LEVEL, CONFIG_UINT32_WORLD_CHAN_CD, @@ -269,6 +269,7 @@ enum eConfigUInt32Values CONFIG_UINT32_CREATURE_SUMMON_LIMIT, CONFIG_UINT32_WAR_EFFORT_AUTOCOMPLETE_PERIOD, CONFIG_UINT32_ACCOUNT_CONCURRENT_AUCTION_LIMIT, + CONFIG_UINT32_INVITE_RESTRICT_CAP, CONFIG_UINT32_VALUE_COUNT }; @@ -472,6 +473,7 @@ enum eConfigBoolValues CONFIG_BOOL_ACCURATE_SPELL_EFFECTS, CONFIG_BOOL_ACCURATE_PVE_EVENTS, CONFIG_BOOL_ACCURATE_LFG, + CONFIG_BOOL_ENABLE_INVITES, CONFIG_BOOL_VALUE_COUNT }; diff --git a/src/game/WorldSession.cpp b/src/game/WorldSession.cpp index acfcbbe5..9594f985 100644 --- a/src/game/WorldSession.cpp +++ b/src/game/WorldSession.cpp @@ -40,6 +40,7 @@ #include "BattleGroundMgr.h" #include "MapManager.h" #include "SocialMgr.h" +#include "Config/Config.h" #include "PlayerBotMgr.h" #include "Anticheat.h" @@ -876,6 +877,14 @@ void WorldSession::SendNotification(int32 string_id, ...) } } +void WorldSession::SendRestrictedHelp(int32 entry) +{ + SendNotification( + entry, sWorld.getConfig(CONFIG_UINT32_INVITE_RESTRICT_CAP), + sConfig.GetStringDefault("Invites.HelpURL", "").c_str() + ); +} + const char * WorldSession::GetMangosString(int32 entry) const { return sObjectMgr.GetMangosString(entry, GetSessionDbLocaleIndex()); @@ -1388,3 +1397,9 @@ bool WorldSession::CharacterScreenIdleKick(uint32 currTime) return false; } + +bool WorldSession::IsAccountRestricted() const +{ + return sWorld.getConfig(CONFIG_BOOL_ENABLE_INVITES) && !GetInviteID() + && GetAccountMaxLevel() < sWorld.getConfig(CONFIG_UINT32_INVITE_RESTRICT_CAP); +} \ No newline at end of file diff --git a/src/game/WorldSession.h b/src/game/WorldSession.h index 026cc1b6..3ccf088f 100644 --- a/src/game/WorldSession.h +++ b/src/game/WorldSession.h @@ -539,7 +539,13 @@ class MANGOS_DLL_SPEC WorldSession void SetAccountFlags(uint32 f) { _accountFlags = f; } uint32 GetAccountFlags() const { return _accountFlags; } - uint32 _accountFlags; + void SetInviteID(uint32 id) { _inviteID = id; } + bool IsAccountRestricted() const; + void SendRestrictedHelp(int32 entry); + uint32 GetInviteID() const { return _inviteID; } + uint32 _accountFlags; + uint32 _inviteID; + bool _restricted; uint32 m_idleTime; diff --git a/src/mangosd/mangosd.conf.dist.in b/src/mangosd/mangosd.conf.dist.in index 118adc06..6c70934f 100644 --- a/src/mangosd/mangosd.conf.dist.in +++ b/src/mangosd/mangosd.conf.dist.in @@ -1880,7 +1880,7 @@ WorldChan.MinLevel = 1 WorldChan.Cooldown = 0 WorldChan.CooldownMaxLevel = 0 WorldChan.CooldownScaling = 0 -WhisperDiffZone.MinLevel = 1 +Whisper.MinLevel = 1 YellRange.LinearScale.MaxLevel = 0 YellRange.QuadraticScale.MaxLevel = 0 ChannelInvite.MinLevel = 10 @@ -1889,6 +1889,11 @@ SayMinLevel = 0; SayEmoteMinLevel = 0; YellMinLevel = 0; +# Invite system (limits trades/whispers/invites until above RestrictionCap or referred by an existing player) +Invites.Enable = false +Invites.RestrictionCap = 15 +Invites.HelpURL = https://lightshope.org/invite + # Database logs LogsDB.Chat = 0 LogsDB.Characters = 0