feat(Core): Implement account play time restrictions (CAIS) (#24576)
Co-authored-by: ratkosrb <35845488+ratkosrb@users.noreply.github.com> Co-authored-by: sudlud <sudlud@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: DDuarte <530940+DDuarte@users.noreply.github.com>
This commit is contained in:
@@ -3421,6 +3421,24 @@ DungeonAccessRequirements.OptionalStringID = 0
|
|||||||
#
|
#
|
||||||
###################################################################################################
|
###################################################################################################
|
||||||
|
|
||||||
|
###################################################################################################
|
||||||
|
# PLAY TIME LIMIT (CAIS)
|
||||||
|
#
|
||||||
|
# CAIS.Enable
|
||||||
|
# Description: Enable the Chinese realm consecutive play time restrictions. After 3
|
||||||
|
# hours of consecutive play, XP and looted money are halved; after 5
|
||||||
|
# hours, the character can no longer gain XP, loot, or turn in quests.
|
||||||
|
# Staying offline for 5 hours resets the accumulated time. The 3h/5h
|
||||||
|
# thresholds are hardcoded client-side.
|
||||||
|
# Note: A worldserver restart resets the accumulated time for every account.
|
||||||
|
# Default: 0 - Disabled
|
||||||
|
# 1 - Enabled
|
||||||
|
|
||||||
|
CAIS.Enable = 0
|
||||||
|
|
||||||
|
#
|
||||||
|
###################################################################################################
|
||||||
|
|
||||||
###################################################################################################
|
###################################################################################################
|
||||||
# DUNGEON AND BATTLEGROUND FINDER
|
# DUNGEON AND BATTLEGROUND FINDER
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -2393,6 +2393,10 @@ namespace lfg
|
|||||||
if (!reward)
|
if (!reward)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// CAIS full restriction: grant no dungeon reward and leave the daily available for later
|
||||||
|
if (player->HasPlayerFlag(PLAYER_FLAGS_NO_PLAY_TIME))
|
||||||
|
continue;
|
||||||
|
|
||||||
bool done = false;
|
bool done = false;
|
||||||
Quest const* quest = sObjectMgr->GetQuestTemplate(reward->firstQuest);
|
Quest const* quest = sObjectMgr->GetQuestTemplate(reward->firstQuest);
|
||||||
if (!quest)
|
if (!quest)
|
||||||
|
|||||||
@@ -2402,24 +2402,16 @@ void Player::SendLogXPGain(uint32 GivenXP, Unit* victim, uint32 BonusXP, bool re
|
|||||||
void Player::GiveXP(uint32 xp, Unit* victim, float group_rate, bool isLFGReward)
|
void Player::GiveXP(uint32 xp, Unit* victim, float group_rate, bool isLFGReward)
|
||||||
{
|
{
|
||||||
if (xp < 1)
|
if (xp < 1)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsAlive() && !GetBattlegroundId() && !isLFGReward)
|
if (!IsAlive() && !GetBattlegroundId() && !isLFGReward)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (HasPlayerFlag(PLAYER_FLAGS_NO_XP_GAIN))
|
if (HasPlayerFlag(PLAYER_FLAGS_NO_XP_GAIN) || HasPlayerFlag(PLAYER_FLAGS_NO_PLAY_TIME))
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (victim && victim->IsCreature() && !victim->ToCreature()->hasLootRecipient())
|
if (victim && victim->IsCreature() && !victim->ToCreature()->hasLootRecipient())
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
uint8 level = GetLevel();
|
uint8 level = GetLevel();
|
||||||
sScriptMgr->OnPlayerBeforeGetLevelForXPGain(this, level);
|
sScriptMgr->OnPlayerBeforeGetLevelForXPGain(this, level);
|
||||||
@@ -2444,6 +2436,9 @@ void Player::GiveXP(uint32 xp, Unit* victim, float group_rate, bool isLFGReward)
|
|||||||
if (level >= maxLevel)
|
if (level >= maxLevel)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (HasPlayerFlag(PLAYER_FLAGS_PARTIAL_PLAY_TIME))
|
||||||
|
xp = std::max(1u, xp / 2);
|
||||||
|
|
||||||
uint32 bonus_xp = 0;
|
uint32 bonus_xp = 0;
|
||||||
bool recruitAFriend = GetsRecruitAFriendBonus(true);
|
bool recruitAFriend = GetsRecruitAFriendBonus(true);
|
||||||
|
|
||||||
@@ -2456,9 +2451,7 @@ void Player::GiveXP(uint32 xp, Unit* victim, float group_rate, bool isLFGReward)
|
|||||||
// hooks and multipliers can modify the xp with a zero or negative value
|
// hooks and multipliers can modify the xp with a zero or negative value
|
||||||
// check again before sending invalid xp to the client
|
// check again before sending invalid xp to the client
|
||||||
if (xp < 1)
|
if (xp < 1)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
SendLogXPGain(xp, victim, bonus_xp, recruitAFriend, group_rate);
|
SendLogXPGain(xp, victim, bonus_xp, recruitAFriend, group_rate);
|
||||||
|
|
||||||
|
|||||||
@@ -468,8 +468,8 @@ enum PlayerFlags : uint32
|
|||||||
PLAYER_FLAGS_IN_PVP = 0x00000200,
|
PLAYER_FLAGS_IN_PVP = 0x00000200,
|
||||||
PLAYER_FLAGS_HIDE_HELM = 0x00000400,
|
PLAYER_FLAGS_HIDE_HELM = 0x00000400,
|
||||||
PLAYER_FLAGS_HIDE_CLOAK = 0x00000800,
|
PLAYER_FLAGS_HIDE_CLOAK = 0x00000800,
|
||||||
PLAYER_FLAGS_PLAYED_LONG_TIME = 0x00001000, // played long time
|
PLAYER_FLAGS_PARTIAL_PLAY_TIME = 0x00001000, // played long time
|
||||||
PLAYER_FLAGS_PLAYED_TOO_LONG = 0x00002000, // played too long time
|
PLAYER_FLAGS_NO_PLAY_TIME = 0x00002000, // played too long time
|
||||||
PLAYER_FLAGS_IS_OUT_OF_BOUNDS = 0x00004000,
|
PLAYER_FLAGS_IS_OUT_OF_BOUNDS = 0x00004000,
|
||||||
PLAYER_FLAGS_DEVELOPER = 0x00008000, // <Dev> prefix for something?
|
PLAYER_FLAGS_DEVELOPER = 0x00008000, // <Dev> prefix for something?
|
||||||
PLAYER_FLAGS_UNK16 = 0x00010000, // pre-3.0.3 PLAYER_FLAGS_SANCTUARY flag for player entered sanctuary
|
PLAYER_FLAGS_UNK16 = 0x00010000, // pre-3.0.3 PLAYER_FLAGS_SANCTUARY flag for player entered sanctuary
|
||||||
|
|||||||
@@ -474,6 +474,16 @@ bool Player::CanRewardQuest(Quest const* quest, uint32 reward, bool msg)
|
|||||||
if (!CanRewardQuest(quest, msg))
|
if (!CanRewardQuest(quest, msg))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// gate the actual turn-in here rather than in the overload above, which LFG also uses
|
||||||
|
// as a "already did today's random" probe
|
||||||
|
if (HasPlayerFlag(PLAYER_FLAGS_NO_PLAY_TIME))
|
||||||
|
{
|
||||||
|
if (msg)
|
||||||
|
GetSession()->SendPlayTimeWarning(PTF_UNHEALTHY_TIME, 0);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
ItemPosCountVec dest;
|
ItemPosCountVec dest;
|
||||||
if (quest->GetRewChoiceItemsCount() > 0)
|
if (quest->GetRewChoiceItemsCount() > 0)
|
||||||
{
|
{
|
||||||
@@ -621,6 +631,9 @@ void Player::CompleteQuest(uint32 quest_id)
|
|||||||
Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id);
|
Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id);
|
||||||
if (qInfo && qInfo->HasFlag(QUEST_FLAGS_TRACKING))
|
if (qInfo && qInfo->HasFlag(QUEST_FLAGS_TRACKING))
|
||||||
{
|
{
|
||||||
|
// auto-rewarded, so CAIS cannot gate it here: the status is already COMPLETE above and
|
||||||
|
// nothing re-triggers the quest, which would destroy the reward rather than defer it.
|
||||||
|
// RewardQuest still zeroes the money and GiveXP still blocks the XP.
|
||||||
RewardQuest(qInfo, 0, this, false);
|
RewardQuest(qInfo, 0, this, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -766,6 +779,16 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver,
|
|||||||
moneyRew += rewOrReqMoney;
|
moneyRew += rewOrReqMoney;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CAIS reduces quest money, mirroring looted money and XP. Applied here as well as at the
|
||||||
|
// turn-in gate because LFG and auto-complete quests reach RewardQuest without CanRewardQuest.
|
||||||
|
if (moneyRew > 0)
|
||||||
|
{
|
||||||
|
if (HasPlayerFlag(PLAYER_FLAGS_NO_PLAY_TIME))
|
||||||
|
moneyRew = 0;
|
||||||
|
else if (HasPlayerFlag(PLAYER_FLAGS_PARTIAL_PLAY_TIME))
|
||||||
|
moneyRew /= 2;
|
||||||
|
}
|
||||||
|
|
||||||
if (moneyRew)
|
if (moneyRew)
|
||||||
{
|
{
|
||||||
ModifyMoney(moneyRew);
|
ModifyMoney(moneyRew);
|
||||||
|
|||||||
@@ -5098,6 +5098,20 @@ bool Player::LoadFromDB(ObjectGuid playerGuid, CharacterDatabaseQueryHolder cons
|
|||||||
SetByteValue(PLAYER_BYTES_3, 0, fields[5].Get<uint8>());
|
SetByteValue(PLAYER_BYTES_3, 0, fields[5].Get<uint8>());
|
||||||
SetByteValue(PLAYER_BYTES_3, 1, fields[54].Get<uint8>());
|
SetByteValue(PLAYER_BYTES_3, 1, fields[54].Get<uint8>());
|
||||||
ReplaceAllPlayerFlags((PlayerFlags)fields[16].Get<uint32>());
|
ReplaceAllPlayerFlags((PlayerFlags)fields[16].Get<uint32>());
|
||||||
|
|
||||||
|
RemovePlayerFlag(PLAYER_FLAGS_NO_PLAY_TIME);
|
||||||
|
RemovePlayerFlag(PLAYER_FLAGS_PARTIAL_PLAY_TIME);
|
||||||
|
|
||||||
|
if (GetSession()->IsAffectedByCAIS())
|
||||||
|
{
|
||||||
|
Seconds const accountPlayedTime = GetSession()->GetConsecutivePlayTime(GameTime::GetGameTime());
|
||||||
|
|
||||||
|
if (accountPlayedTime >= PLAY_TIME_LIMIT_FULL)
|
||||||
|
SetPlayerFlag(PLAYER_FLAGS_NO_PLAY_TIME);
|
||||||
|
else if (accountPlayedTime >= PLAY_TIME_LIMIT_PARTIAL)
|
||||||
|
SetPlayerFlag(PLAYER_FLAGS_PARTIAL_PLAY_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
SetInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX, fields[53].Get<uint32>());
|
SetInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX, fields[53].Get<uint32>());
|
||||||
|
|
||||||
SetUInt64Value(PLAYER_FIELD_KNOWN_CURRENCIES, fields[52].Get<uint64>());
|
SetUInt64Value(PLAYER_FIELD_KNOWN_CURRENCIES, fields[52].Get<uint64>());
|
||||||
|
|||||||
@@ -2115,9 +2115,7 @@ GroupJoinBattlegroundResult Group::CanJoinBattlegroundQueue(Battleground const*
|
|||||||
// check if someone in party is using dungeon system
|
// check if someone in party is using dungeon system
|
||||||
lfg::LfgState lfgState = sLFGMgr->GetState(member->GetGUID());
|
lfg::LfgState lfgState = sLFGMgr->GetState(member->GetGUID());
|
||||||
if (lfgState > lfg::LFG_STATE_NONE && (lfgState != lfg::LFG_STATE_QUEUED || !sWorld->getBoolConfig(CONFIG_ALLOW_JOIN_BG_AND_LFG)))
|
if (lfgState > lfg::LFG_STATE_NONE && (lfgState != lfg::LFG_STATE_QUEUED || !sWorld->getBoolConfig(CONFIG_ALLOW_JOIN_BG_AND_LFG)))
|
||||||
{
|
|
||||||
return ERR_LFG_CANT_USE_BATTLEGROUND;
|
return ERR_LFG_CANT_USE_BATTLEGROUND;
|
||||||
}
|
|
||||||
|
|
||||||
// pussywizard: prevent joining when any member is in bg/arena
|
// pussywizard: prevent joining when any member is in bg/arena
|
||||||
if (member->InBattleground())
|
if (member->InBattleground())
|
||||||
@@ -2129,9 +2127,7 @@ GroupJoinBattlegroundResult Group::CanJoinBattlegroundQueue(Battleground const*
|
|||||||
|
|
||||||
// don't let join if someone from the group is already in that bg queue
|
// don't let join if someone from the group is already in that bg queue
|
||||||
if (member->InBattlegroundQueueForBattlegroundQueueType(bgQueueTypeId))
|
if (member->InBattlegroundQueueForBattlegroundQueueType(bgQueueTypeId))
|
||||||
{
|
|
||||||
return ERR_BATTLEGROUND_JOIN_FAILED;
|
return ERR_BATTLEGROUND_JOIN_FAILED;
|
||||||
}
|
|
||||||
|
|
||||||
// don't let join if someone from the group is in bg queue random
|
// don't let join if someone from the group is in bg queue random
|
||||||
if (member->InBattlegroundQueueForBattlegroundQueueType(bgQueueTypeIdRandom))
|
if (member->InBattlegroundQueueForBattlegroundQueueType(bgQueueTypeIdRandom))
|
||||||
@@ -2146,9 +2142,10 @@ GroupJoinBattlegroundResult Group::CanJoinBattlegroundQueue(Battleground const*
|
|||||||
return ERR_GROUP_JOIN_BATTLEGROUND_FAIL;
|
return ERR_GROUP_JOIN_BATTLEGROUND_FAIL;
|
||||||
|
|
||||||
if (!member->GetBGAccessByLevel(bgTemplate->GetBgTypeID()))
|
if (!member->GetBGAccessByLevel(bgTemplate->GetBgTypeID()))
|
||||||
{
|
|
||||||
return ERR_BATTLEGROUND_JOIN_TIMED_OUT;
|
return ERR_BATTLEGROUND_JOIN_TIMED_OUT;
|
||||||
}
|
|
||||||
|
if (member->HasPlayerFlag(PLAYER_FLAGS_NO_PLAY_TIME)) // Assumed to only apply to full restriction rather than partial
|
||||||
|
return ERR_GROUP_JOIN_BATTLEGROUND_FAIL; // ERR_ARENA_EXPIRED_CAIS does not seem to be a result, so using this error instead
|
||||||
}
|
}
|
||||||
|
|
||||||
// for arenas: check party size is proper
|
// for arenas: check party size is proper
|
||||||
|
|||||||
@@ -15,17 +15,28 @@
|
|||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "GameTime.h"
|
||||||
#include "Opcodes.h"
|
#include "Opcodes.h"
|
||||||
#include "WorldPacket.h"
|
#include "WorldPacket.h"
|
||||||
#include "WorldSession.h"
|
#include "WorldSession.h"
|
||||||
|
|
||||||
void WorldSession::SendAuthResponse(uint8 code, bool shortForm, uint32 queuePos)
|
void WorldSession::SendAuthResponse(uint8 code, bool shortForm, uint32 queuePos)
|
||||||
{
|
{
|
||||||
|
// BillingTimeRested: seconds of "healthy" play left before CAIS halves XP/loot from creatures
|
||||||
|
// and quests (read by the client's GetBillingTimeRested()); 0 unless the CAIS flag is set.
|
||||||
|
uint32 billingTimeRested = 0;
|
||||||
|
if (IsAffectedByCAIS())
|
||||||
|
{
|
||||||
|
Seconds const played = GetConsecutivePlayTime(GameTime::GetGameTime());
|
||||||
|
if (played < PLAY_TIME_LIMIT_PARTIAL)
|
||||||
|
billingTimeRested = uint32((PLAY_TIME_LIMIT_PARTIAL - played).count());
|
||||||
|
}
|
||||||
|
|
||||||
WorldPacket packet(SMSG_AUTH_RESPONSE, 1 + 4 + 1 + 4 + 1 + (shortForm ? 0 : (4 + 1)));
|
WorldPacket packet(SMSG_AUTH_RESPONSE, 1 + 4 + 1 + 4 + 1 + (shortForm ? 0 : (4 + 1)));
|
||||||
packet << uint8(code);
|
packet << uint8(code);
|
||||||
packet << uint32(0); // BillingTimeRemaining
|
packet << uint32(0); // BillingTimeRemaining
|
||||||
packet << GetBillingPlanFlags();
|
packet << GetBillingPlanFlags();
|
||||||
packet << uint32(0); // BillingTimeRested
|
packet << billingTimeRested;
|
||||||
uint8 exp = Expansion(); // 0 - normal, 1 - TBC, 2 - WotLK, must be set in database manually for each account
|
uint8 exp = Expansion(); // 0 - normal, 1 - TBC, 2 - WotLK, must be set in database manually for each account
|
||||||
|
|
||||||
if (exp >= MAX_EXPANSIONS)
|
if (exp >= MAX_EXPANSIONS)
|
||||||
|
|||||||
@@ -192,6 +192,8 @@ void WorldSession::HandleBattlemasterJoinOpcode(WorldPacket& recvData)
|
|||||||
{
|
{
|
||||||
err = ERR_BATTLEGROUND_NONE;
|
err = ERR_BATTLEGROUND_NONE;
|
||||||
}
|
}
|
||||||
|
else if (_player->HasPlayerFlag(PLAYER_FLAGS_NO_PLAY_TIME)) // Assumed to only apply to full restriction rather than partial
|
||||||
|
err = ERR_GROUP_JOIN_BATTLEGROUND_FAIL; // ERR_ARENA_EXPIRED_CAIS does not seem to be a result, so using this error instead
|
||||||
|
|
||||||
if (err <= 0)
|
if (err <= 0)
|
||||||
{
|
{
|
||||||
@@ -791,13 +793,11 @@ void WorldSession::HandleBattlemasterJoinArena(WorldPacket& recvData)
|
|||||||
{
|
{
|
||||||
lfg::LfgState lfgState = sLFGMgr->GetState(GetPlayer()->GetGUID());
|
lfg::LfgState lfgState = sLFGMgr->GetState(GetPlayer()->GetGUID());
|
||||||
if (GetPlayer()->InBattleground()) // currently in battleground
|
if (GetPlayer()->InBattleground()) // currently in battleground
|
||||||
{
|
|
||||||
err = ERR_BATTLEGROUND_NOT_IN_BATTLEGROUND;
|
err = ERR_BATTLEGROUND_NOT_IN_BATTLEGROUND;
|
||||||
}
|
|
||||||
else if (lfgState > lfg::LFG_STATE_NONE && (lfgState != lfg::LFG_STATE_QUEUED || !sWorld->getBoolConfig(CONFIG_ALLOW_JOIN_BG_AND_LFG))) // using lfg system
|
else if (lfgState > lfg::LFG_STATE_NONE && (lfgState != lfg::LFG_STATE_QUEUED || !sWorld->getBoolConfig(CONFIG_ALLOW_JOIN_BG_AND_LFG))) // using lfg system
|
||||||
{
|
|
||||||
err = ERR_LFG_CANT_USE_BATTLEGROUND;
|
err = ERR_LFG_CANT_USE_BATTLEGROUND;
|
||||||
}
|
else if (GetPlayer()->HasPlayerFlag(PLAYER_FLAGS_NO_PLAY_TIME)) // Assumed to only apply to full restriction rather than partial
|
||||||
|
err = ERR_GROUP_JOIN_BATTLEGROUND_FAIL; // ERR_ARENA_EXPIRED_CAIS does not seem to be a result, so using this error instead
|
||||||
|
|
||||||
if (err <= 0)
|
if (err <= 0)
|
||||||
{
|
{
|
||||||
@@ -870,9 +870,7 @@ void WorldSession::HandleBattlemasterJoinArena(WorldPacket& recvData)
|
|||||||
grp->DoForAllMembers([&bgQueue, &err](Player* member)
|
grp->DoForAllMembers([&bgQueue, &err](Player* member)
|
||||||
{
|
{
|
||||||
if (bgQueue.IsPlayerInvitedToRatedArena(member->GetGUID()))
|
if (bgQueue.IsPlayerInvitedToRatedArena(member->GetGUID()))
|
||||||
{
|
|
||||||
err = ERR_BATTLEGROUND_JOIN_FAILED;
|
err = ERR_BATTLEGROUND_JOIN_FAILED;
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -559,6 +559,12 @@ void WorldSession::HandleLootRoll(WorldPacket& recvData)
|
|||||||
if (!group)
|
if (!group)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (GetPlayer()->HasPlayerFlag(PLAYER_FLAGS_NO_PLAY_TIME))
|
||||||
|
{
|
||||||
|
SendPlayTimeWarning(PTF_UNHEALTHY_TIME, 0);
|
||||||
|
rollType = ROLL_PASS;
|
||||||
|
}
|
||||||
|
|
||||||
group->CountRollVote(GetPlayer()->GetGUID(), guid, rollType);
|
group->CountRollVote(GetPlayer()->GetGUID(), guid, rollType);
|
||||||
|
|
||||||
switch (rollType)
|
switch (rollType)
|
||||||
|
|||||||
@@ -40,6 +40,14 @@ void WorldSession::HandleAutostoreLootItemOpcode(WorldPacket& recvData)
|
|||||||
|
|
||||||
recvData >> lootSlot;
|
recvData >> lootSlot;
|
||||||
|
|
||||||
|
// full CAIS restriction blocks item pickup from every loot source (GO/gather/item/corpse),
|
||||||
|
// not just the creature-corpse window guarded in HandleLootOpcode
|
||||||
|
if (player->HasPlayerFlag(PLAYER_FLAGS_NO_PLAY_TIME))
|
||||||
|
{
|
||||||
|
player->SendLootError(lguid, LOOT_ERROR_PLAY_TIME_EXCEEDED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (lguid.IsGameObject())
|
if (lguid.IsGameObject())
|
||||||
{
|
{
|
||||||
GameObject* go = player->GetMap()->GetGameObject(lguid);
|
GameObject* go = player->GetMap()->GetGameObject(lguid);
|
||||||
@@ -179,6 +187,15 @@ void WorldSession::HandleLootMoneyOpcode(WorldPacket& /*recvData*/)
|
|||||||
|
|
||||||
if (loot)
|
if (loot)
|
||||||
{
|
{
|
||||||
|
// the money is zeroed and dropped from storage below no matter who is paid, so a fully
|
||||||
|
// restricted looter would destroy it rather than receive it. Bail before that teardown;
|
||||||
|
// reachable for windows not opened through HandleLootOpcode, e.g. chests and lockboxes.
|
||||||
|
if (player->HasPlayerFlag(PLAYER_FLAGS_NO_PLAY_TIME))
|
||||||
|
{
|
||||||
|
player->SendLootError(guid, LOOT_ERROR_PLAY_TIME_EXCEEDED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
sScriptMgr->OnPlayerBeforeLootMoney(player, loot);
|
sScriptMgr->OnPlayerBeforeLootMoney(player, loot);
|
||||||
loot->NotifyMoneyRemoved();
|
loot->NotifyMoneyRemoved();
|
||||||
if (shareMoney && player->GetGroup()) //item, pickpocket and players can be looted only single player
|
if (shareMoney && player->GetGroup()) //item, pickpocket and players can be looted only single player
|
||||||
@@ -200,27 +217,59 @@ void WorldSession::HandleLootMoneyOpcode(WorldPacket& /*recvData*/)
|
|||||||
|
|
||||||
for (std::vector<Player*>::const_iterator i = playersNear.begin(); i != playersNear.end(); ++i)
|
for (std::vector<Player*>::const_iterator i = playersNear.begin(); i != playersNear.end(); ++i)
|
||||||
{
|
{
|
||||||
(*i)->ModifyMoney(goldPerPlayer);
|
uint32 finalGold = goldPerPlayer;
|
||||||
(*i)->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY, goldPerPlayer);
|
|
||||||
|
if ((*i)->HasPlayerFlag(PLAYER_FLAGS_NO_PLAY_TIME))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((*i)->HasPlayerFlag(PLAYER_FLAGS_PARTIAL_PLAY_TIME))
|
||||||
|
{
|
||||||
|
finalGold /= 2;
|
||||||
|
|
||||||
|
// a halved share that rounds down to nothing is not worth announcing
|
||||||
|
if (!finalGold)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*i)->ModifyMoney(finalGold);
|
||||||
|
(*i)->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY, finalGold);
|
||||||
|
|
||||||
WorldPacket data(SMSG_LOOT_MONEY_NOTIFY, 4 + 1);
|
WorldPacket data(SMSG_LOOT_MONEY_NOTIFY, 4 + 1);
|
||||||
data << uint32(goldPerPlayer);
|
data << uint32(finalGold);
|
||||||
data << uint8(playersNear.size() > 1 ? 0 : 1); // Controls the text displayed in chat. 0 is "Your share is..." and 1 is "You loot..."
|
data << uint8(playersNear.size() > 1 ? 0 : 1); // Controls the text displayed in chat. 0 is "Your share is..." and 1 is "You loot..."
|
||||||
(*i)->SendDirectMessage(&data);
|
(*i)->SendDirectMessage(&data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sScriptMgr->OnPlayerAfterCreatureLootMoney(player);
|
uint32 finalGold = loot->gold;
|
||||||
player->ModifyMoney(loot->gold);
|
bool award = true; // full restriction already returned above
|
||||||
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY, loot->gold);
|
|
||||||
|
|
||||||
WorldPacket data(SMSG_LOOT_MONEY_NOTIFY, 4 + 1);
|
if (player->HasPlayerFlag(PLAYER_FLAGS_PARTIAL_PLAY_TIME))
|
||||||
data << uint32(loot->gold);
|
{
|
||||||
data << uint8(1); // "You loot..."
|
finalGold /= 2;
|
||||||
SendPacket(&data);
|
|
||||||
|
// a halved amount that rounds down to nothing is not worth announcing
|
||||||
|
award = finalGold != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fire the hook regardless of the CAIS reduction, matching OnLootMoney below
|
||||||
|
sScriptMgr->OnPlayerAfterCreatureLootMoney(player);
|
||||||
|
|
||||||
|
if (award)
|
||||||
|
{
|
||||||
|
player->ModifyMoney(finalGold);
|
||||||
|
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY, finalGold);
|
||||||
|
|
||||||
|
WorldPacket data(SMSG_LOOT_MONEY_NOTIFY, 4 + 1);
|
||||||
|
data << uint32(finalGold);
|
||||||
|
data << uint8(1); // "You loot..."
|
||||||
|
SendPacket(&data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reports the amount that dropped, not the CAIS-reduced amount actually awarded;
|
||||||
|
// a script that grants money from this hook bypasses the play time restriction
|
||||||
sScriptMgr->OnLootMoney(player, loot->gold);
|
sScriptMgr->OnLootMoney(player, loot->gold);
|
||||||
|
|
||||||
loot->gold = 0;
|
loot->gold = 0;
|
||||||
@@ -242,15 +291,23 @@ void WorldSession::HandleLootOpcode(WorldPacket& recvData)
|
|||||||
ObjectGuid guid;
|
ObjectGuid guid;
|
||||||
recvData >> guid;
|
recvData >> guid;
|
||||||
|
|
||||||
|
Player* player = GetPlayer();
|
||||||
|
|
||||||
// Check possible cheat
|
// Check possible cheat
|
||||||
if (!GetPlayer()->IsAlive() || !guid.IsCreatureOrVehicle())
|
if (!player->IsAlive() || !guid.IsCreatureOrVehicle())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// interrupt cast
|
if (player->HasPlayerFlag(PLAYER_FLAGS_NO_PLAY_TIME))
|
||||||
if (GetPlayer()->IsNonMeleeSpellCast(false))
|
{
|
||||||
GetPlayer()->InterruptNonMeleeSpells(false);
|
player->SendLootError(guid, LOOT_ERROR_PLAY_TIME_EXCEEDED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
GetPlayer()->SendLoot(guid, LOOT_CORPSE);
|
// interrupt cast
|
||||||
|
if (player->IsNonMeleeSpellCast(false))
|
||||||
|
player->InterruptNonMeleeSpells(false);
|
||||||
|
|
||||||
|
player->SendLoot(guid, LOOT_CORPSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WorldSession::HandleLootReleaseOpcode(WorldPacket& recvData)
|
void WorldSession::HandleLootReleaseOpcode(WorldPacket& recvData)
|
||||||
@@ -446,7 +503,7 @@ void WorldSession::HandleLootMasterGiveOpcode(WorldPacket& recvData)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_player->IsInRaidWith(target))
|
if (!_player->IsInRaidWith(target) || target->HasPlayerFlag(PLAYER_FLAGS_NO_PLAY_TIME))
|
||||||
{
|
{
|
||||||
_player->SendLootError(lootguid, LOOT_ERROR_MASTER_OTHER);
|
_player->SendLootError(lootguid, LOOT_ERROR_MASTER_OTHER);
|
||||||
//LOG_DEBUG("network", "MasterLootItem: Player {} tried to give an item to ineligible player {} !", GetPlayer()->GetName(), target->GetName());
|
//LOG_DEBUG("network", "MasterLootItem: Player {} tried to give an item to ineligible player {} !", GetPlayer()->GetName(), target->GetName());
|
||||||
|
|||||||
@@ -168,3 +168,11 @@ WorldPacket const* WorldPackets::Misc::ComplainResult::Write()
|
|||||||
|
|
||||||
return &_worldPacket;
|
return &_worldPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WorldPacket const* WorldPackets::Misc::PlayTimeWarning::Write()
|
||||||
|
{
|
||||||
|
_worldPacket << Flag;
|
||||||
|
_worldPacket << PlayTimeRemaining;
|
||||||
|
|
||||||
|
return &_worldPacket;
|
||||||
|
}
|
||||||
|
|||||||
@@ -236,6 +236,17 @@ namespace WorldPackets
|
|||||||
|
|
||||||
uint8 Unk = 0;
|
uint8 Unk = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class PlayTimeWarning final : public ServerPacket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PlayTimeWarning() : ServerPacket(SMSG_PLAY_TIME_WARNING, 4 + 4) {}
|
||||||
|
|
||||||
|
WorldPacket const* Write() override;
|
||||||
|
|
||||||
|
uint32 Flag = 0; // PlayTimeFlag mask, set by WorldSession::SendPlayTimeWarning
|
||||||
|
int32 PlayTimeRemaining = 0;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
#include "MapMgr.h"
|
#include "MapMgr.h"
|
||||||
#include "Metric.h"
|
#include "Metric.h"
|
||||||
|
#include "MiscPackets.h"
|
||||||
#include "ObjectAccessor.h"
|
#include "ObjectAccessor.h"
|
||||||
#include "ObjectMgr.h"
|
#include "ObjectMgr.h"
|
||||||
#include "Opcodes.h"
|
#include "Opcodes.h"
|
||||||
@@ -121,6 +122,9 @@ WorldSession::WorldSession(uint32 id, std::string&& name, uint32 accountFlags, s
|
|||||||
_accountFlags(accountFlags),
|
_accountFlags(accountFlags),
|
||||||
m_expansion(expansion),
|
m_expansion(expansion),
|
||||||
m_total_time(TotalTime),
|
m_total_time(TotalTime),
|
||||||
|
_lastUpdateTime(GameTime::GetGameTime()),
|
||||||
|
_createTime(GameTime::GetGameTime()),
|
||||||
|
_previousPlayTime(0),
|
||||||
_logoutTime(0),
|
_logoutTime(0),
|
||||||
m_inQueue(false),
|
m_inQueue(false),
|
||||||
m_playerLoading(false),
|
m_playerLoading(false),
|
||||||
@@ -227,6 +231,14 @@ bool WorldSession::IsRecurringBillingAccount() const
|
|||||||
return HasAccountFlag(ACCOUNT_FLAG_RECURRING_BILLING);
|
return HasAccountFlag(ACCOUNT_FLAG_RECURRING_BILLING);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WorldSession::IsAffectedByCAIS() const
|
||||||
|
{
|
||||||
|
// China realm system for restricting consecutive play time (anti-addiction).
|
||||||
|
// There is no known per-account flag for it, so a realm-wide config gate is used;
|
||||||
|
// swap this out if a per-account/realm-region source is found later.
|
||||||
|
return sWorld->getBoolConfig(CONFIG_CAIS_ENABLED);
|
||||||
|
}
|
||||||
|
|
||||||
uint8 WorldSession::GetBillingPlanFlags() const
|
uint8 WorldSession::GetBillingPlanFlags() const
|
||||||
{
|
{
|
||||||
uint8 flags = SESSION_NONE;
|
uint8 flags = SESSION_NONE;
|
||||||
@@ -240,6 +252,9 @@ uint8 WorldSession::GetBillingPlanFlags() const
|
|||||||
if (IsInternetGameRoomAccount())
|
if (IsInternetGameRoomAccount())
|
||||||
flags |= SESSION_IGR;
|
flags |= SESSION_IGR;
|
||||||
|
|
||||||
|
if (IsAffectedByCAIS())
|
||||||
|
flags |= SESSION_ENABLE_CAIS;
|
||||||
|
|
||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,6 +395,12 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
|
|||||||
uint32 processedPackets = 0;
|
uint32 processedPackets = 0;
|
||||||
time_t currentTime = GameTime::GetGameTime().count();
|
time_t currentTime = GameTime::GetGameTime().count();
|
||||||
|
|
||||||
|
if (GetPlayer() && GetPlayer()->IsInWorld() && IsAffectedByCAIS())
|
||||||
|
{
|
||||||
|
CheckPlayedTimeLimit(Seconds(currentTime));
|
||||||
|
_lastUpdateTime = Seconds(currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
constexpr uint32 MAX_PROCESSED_PACKETS_IN_SAME_WORLDSESSION_UPDATE = 150;
|
constexpr uint32 MAX_PROCESSED_PACKETS_IN_SAME_WORLDSESSION_UPDATE = 150;
|
||||||
|
|
||||||
while (m_Socket && _recvQueue.next(packet, updater))
|
while (m_Socket && _recvQueue.next(packet, updater))
|
||||||
@@ -610,6 +631,47 @@ bool WorldSession::IsSocketClosed() const
|
|||||||
return !m_Socket || !m_Socket->IsOpen();
|
return !m_Socket || !m_Socket->IsOpen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WorldSession::CheckPlayedTimeLimit(Seconds now)
|
||||||
|
{
|
||||||
|
Seconds const previousPlayed = GetConsecutivePlayTime(_lastUpdateTime);
|
||||||
|
Seconds const currentPlayed = GetConsecutivePlayTime(now);
|
||||||
|
|
||||||
|
if ((previousPlayed < PLAY_TIME_LIMIT_FULL) &&
|
||||||
|
(currentPlayed >= PLAY_TIME_LIMIT_FULL))
|
||||||
|
{
|
||||||
|
SendPlayTimeWarning(PTF_UNHEALTHY_TIME, 0);
|
||||||
|
GetPlayer()->SetPlayerFlag(PLAYER_FLAGS_NO_PLAY_TIME);
|
||||||
|
GetPlayer()->RemovePlayerFlag(PLAYER_FLAGS_PARTIAL_PLAY_TIME);
|
||||||
|
}
|
||||||
|
else if ((previousPlayed < PLAY_TIME_LIMIT_APPROACHING_FULL) &&
|
||||||
|
(currentPlayed >= PLAY_TIME_LIMIT_APPROACHING_FULL))
|
||||||
|
{
|
||||||
|
SendPlayTimeWarning(PTF_APPROACHING_NO_PLAY_TIME, int32((PLAY_TIME_LIMIT_FULL - currentPlayed).count()));
|
||||||
|
GetPlayer()->SetPlayerFlag(PLAYER_FLAGS_PARTIAL_PLAY_TIME);
|
||||||
|
GetPlayer()->RemovePlayerFlag(PLAYER_FLAGS_NO_PLAY_TIME);
|
||||||
|
}
|
||||||
|
else if ((previousPlayed < PLAY_TIME_LIMIT_PARTIAL) &&
|
||||||
|
(currentPlayed >= PLAY_TIME_LIMIT_PARTIAL))
|
||||||
|
{
|
||||||
|
SendPlayTimeWarning(PTF_APPROACHING_NO_PLAY_TIME, int32((PLAY_TIME_LIMIT_FULL - currentPlayed).count()));
|
||||||
|
GetPlayer()->SetPlayerFlag(PLAYER_FLAGS_PARTIAL_PLAY_TIME);
|
||||||
|
GetPlayer()->RemovePlayerFlag(PLAYER_FLAGS_NO_PLAY_TIME);
|
||||||
|
}
|
||||||
|
else if ((previousPlayed < PLAY_TIME_LIMIT_APPROACHING_PARTIAL) &&
|
||||||
|
(currentPlayed >= PLAY_TIME_LIMIT_APPROACHING_PARTIAL))
|
||||||
|
{
|
||||||
|
SendPlayTimeWarning(PTF_APPROACHING_PARTIAL_PLAY_TIME, int32((PLAY_TIME_LIMIT_PARTIAL - currentPlayed).count()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WorldSession::SendPlayTimeWarning(PlayTimeFlag flag, int32 playTimeRemaining)
|
||||||
|
{
|
||||||
|
WorldPackets::Misc::PlayTimeWarning playTimeWarning;
|
||||||
|
playTimeWarning.Flag = flag;
|
||||||
|
playTimeWarning.PlayTimeRemaining = playTimeRemaining;
|
||||||
|
SendPacket(playTimeWarning.Write());
|
||||||
|
}
|
||||||
|
|
||||||
/// %Log the player out
|
/// %Log the player out
|
||||||
void WorldSession::LogoutPlayer(bool save)
|
void WorldSession::LogoutPlayer(bool save)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "CircularBuffer.h"
|
#include "CircularBuffer.h"
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include "DatabaseEnv.h"
|
#include "DatabaseEnv.h"
|
||||||
|
#include "Duration.h"
|
||||||
#include "GossipDef.h"
|
#include "GossipDef.h"
|
||||||
#include "Packet.h"
|
#include "Packet.h"
|
||||||
#include "SharedDefines.h"
|
#include "SharedDefines.h"
|
||||||
@@ -289,6 +290,20 @@ enum CharterTypes
|
|||||||
ARENA_TEAM_CHARTER_5v5_TYPE = 5
|
ARENA_TEAM_CHARTER_5v5_TYPE = 5
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr Seconds PLAY_TIME_LIMIT_APPROACHING_PARTIAL = Hours(2) + Minutes(30);
|
||||||
|
constexpr Seconds PLAY_TIME_LIMIT_PARTIAL = Hours(3);
|
||||||
|
constexpr Seconds PLAY_TIME_LIMIT_APPROACHING_FULL = Hours(4) + Minutes(30);
|
||||||
|
constexpr Seconds PLAY_TIME_LIMIT_FULL = Hours(5);
|
||||||
|
|
||||||
|
enum PlayTimeFlag : uint32
|
||||||
|
{
|
||||||
|
PTF_APPROACHING_PARTIAL_PLAY_TIME = 0x1000,
|
||||||
|
PTF_APPROACHING_NO_PLAY_TIME = 0x2000,
|
||||||
|
PTF_UNK_1 = 0x20000000,
|
||||||
|
PTF_UNK_2 = 0x40000000,
|
||||||
|
PTF_UNHEALTHY_TIME = 0x80000000,
|
||||||
|
};
|
||||||
|
|
||||||
//class to deal with packet processing
|
//class to deal with packet processing
|
||||||
//allows to determine if next packet is safe to be processed
|
//allows to determine if next packet is safe to be processed
|
||||||
class PacketFilter
|
class PacketFilter
|
||||||
@@ -405,6 +420,7 @@ public:
|
|||||||
bool IsTrialAccount() const;
|
bool IsTrialAccount() const;
|
||||||
bool IsInternetGameRoomAccount() const;
|
bool IsInternetGameRoomAccount() const;
|
||||||
bool IsRecurringBillingAccount() const;
|
bool IsRecurringBillingAccount() const;
|
||||||
|
bool IsAffectedByCAIS() const;
|
||||||
|
|
||||||
uint8 GetBillingPlanFlags() const;
|
uint8 GetBillingPlanFlags() const;
|
||||||
|
|
||||||
@@ -483,6 +499,16 @@ public:
|
|||||||
/// Session in auth.queue currently
|
/// Session in auth.queue currently
|
||||||
void SetInQueue(bool state) { m_inQueue = state; }
|
void SetInQueue(bool state) { m_inQueue = state; }
|
||||||
|
|
||||||
|
// Playtime limit
|
||||||
|
Seconds GetCreateTime() const { return _createTime; }
|
||||||
|
// Measured from session creation (authentication), so login queue and character select count
|
||||||
|
// toward the limits. Matches the VMaNGOS behaviour this is ported from.
|
||||||
|
Seconds GetConsecutivePlayTime(Seconds now) const { return (now - _createTime) + _previousPlayTime; }
|
||||||
|
Seconds GetPreviousPlayedTime() const { return _previousPlayTime; }
|
||||||
|
void SetPreviousPlayedTime(Seconds playedTime) { _previousPlayTime = playedTime; }
|
||||||
|
void CheckPlayedTimeLimit(Seconds now);
|
||||||
|
void SendPlayTimeWarning(PlayTimeFlag flag, int32 playTimeRemaining);
|
||||||
|
|
||||||
/// Is the user engaged in a log out process?
|
/// Is the user engaged in a log out process?
|
||||||
bool isLogingOut() const { return _logoutTime || m_playerLogout; }
|
bool isLogingOut() const { return _logoutTime || m_playerLogout; }
|
||||||
|
|
||||||
@@ -1255,6 +1281,9 @@ private:
|
|||||||
// Warden
|
// Warden
|
||||||
std::unique_ptr<Warden> _warden; // Remains nullptr if Warden system is not enabled by config
|
std::unique_ptr<Warden> _warden; // Remains nullptr if Warden system is not enabled by config
|
||||||
|
|
||||||
|
Seconds _lastUpdateTime; // last time session was updated by world
|
||||||
|
Seconds _createTime; // when session was created
|
||||||
|
Seconds _previousPlayTime; // play time from previous session less than 5 hours ago
|
||||||
time_t _logoutTime;
|
time_t _logoutTime;
|
||||||
bool m_inQueue; // session wait in auth.queue
|
bool m_inQueue; // session wait in auth.queue
|
||||||
bool m_playerLoading; // code processed in LoginPlayer
|
bool m_playerLoading; // code processed in LoginPlayer
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ WorldSessionMgr::WorldSessionMgr()
|
|||||||
_maxQueuedSessionCount = 0;
|
_maxQueuedSessionCount = 0;
|
||||||
_playerCount = 0;
|
_playerCount = 0;
|
||||||
_maxPlayerCount = 0;
|
_maxPlayerCount = 0;
|
||||||
|
_accountsPlayHistoryPruneTimer = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
WorldSessionMgr::~WorldSessionMgr()
|
WorldSessionMgr::~WorldSessionMgr()
|
||||||
@@ -91,6 +92,22 @@ WorldSession* WorldSessionMgr::FindOfflineSessionForCharacterGUID(ObjectGuid::Lo
|
|||||||
|
|
||||||
void WorldSessionMgr::UpdateSessions(uint32 const diff)
|
void WorldSessionMgr::UpdateSessions(uint32 const diff)
|
||||||
{
|
{
|
||||||
|
// Drop play-history entries past the reset window so the map stays bounded even for
|
||||||
|
// accounts that disconnect and never reconnect (login only erases their own entry).
|
||||||
|
_accountsPlayHistoryPruneTimer += diff;
|
||||||
|
if (_accountsPlayHistoryPruneTimer >= 10 * MINUTE * IN_MILLISECONDS)
|
||||||
|
{
|
||||||
|
_accountsPlayHistoryPruneTimer = 0;
|
||||||
|
Seconds const now = GameTime::GetGameTime();
|
||||||
|
for (auto itr = _accountsPlayHistory.begin(); itr != _accountsPlayHistory.end();)
|
||||||
|
{
|
||||||
|
if ((now - itr->second.logoutTime) >= PLAY_TIME_LIMIT_FULL)
|
||||||
|
itr = _accountsPlayHistory.erase(itr);
|
||||||
|
else
|
||||||
|
++itr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
METRIC_DETAILED_NO_THRESHOLD_TIMER("world_update_time",
|
METRIC_DETAILED_NO_THRESHOLD_TIMER("world_update_time",
|
||||||
METRIC_TAG("type", "Add sessions"),
|
METRIC_TAG("type", "Add sessions"),
|
||||||
@@ -117,8 +134,19 @@ void WorldSessionMgr::UpdateSessions(uint32 const diff)
|
|||||||
// pussywizard:
|
// pussywizard:
|
||||||
if (pSession->HandleSocketClosed())
|
if (pSession->HandleSocketClosed())
|
||||||
{
|
{
|
||||||
|
Seconds const now = GameTime::GetGameTime();
|
||||||
|
|
||||||
|
if (pSession->IsAffectedByCAIS())
|
||||||
|
{
|
||||||
|
// a reconnect builds a fresh session and carries time from _accountsPlayHistory, so persist it
|
||||||
|
// here at socket-close time rather than during offline cleanup (which runs ~60s later)
|
||||||
|
AccountPlayHistory& history = _accountsPlayHistory[pSession->GetAccountId()];
|
||||||
|
history.playedTime = pSession->GetConsecutivePlayTime(now);
|
||||||
|
history.logoutTime = now;
|
||||||
|
}
|
||||||
|
|
||||||
if (!RemoveQueuedPlayer(pSession) && sWorld->getIntConfig(CONFIG_INTERVAL_DISCONNECT_TOLERANCE))
|
if (!RemoveQueuedPlayer(pSession) && sWorld->getIntConfig(CONFIG_INTERVAL_DISCONNECT_TOLERANCE))
|
||||||
_disconnects[pSession->GetAccountId()] = GameTime::GetGameTime().count();
|
_disconnects[pSession->GetAccountId()] = now.count();
|
||||||
_sessions.erase(itr);
|
_sessions.erase(itr);
|
||||||
// there should be no offline session if current one is logged onto a character
|
// there should be no offline session if current one is logged onto a character
|
||||||
SessionMap::iterator iter;
|
SessionMap::iterator iter;
|
||||||
@@ -128,7 +156,7 @@ void WorldSessionMgr::UpdateSessions(uint32 const diff)
|
|||||||
_offlineSessions.erase(iter);
|
_offlineSessions.erase(iter);
|
||||||
delete tmp;
|
delete tmp;
|
||||||
}
|
}
|
||||||
pSession->SetOfflineTime(GameTime::GetGameTime().count());
|
pSession->SetOfflineTime(now.count());
|
||||||
_offlineSessions[pSession->GetAccountId()] = pSession;
|
_offlineSessions[pSession->GetAccountId()] = pSession;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -138,8 +166,17 @@ void WorldSessionMgr::UpdateSessions(uint32 const diff)
|
|||||||
|
|
||||||
if (!pSession->Update(diff, updater))
|
if (!pSession->Update(diff, updater))
|
||||||
{
|
{
|
||||||
|
Seconds const now = GameTime::GetGameTime();
|
||||||
|
|
||||||
|
if (pSession->IsAffectedByCAIS())
|
||||||
|
{
|
||||||
|
AccountPlayHistory& history = _accountsPlayHistory[pSession->GetAccountId()];
|
||||||
|
history.playedTime = pSession->GetConsecutivePlayTime(now);
|
||||||
|
history.logoutTime = now;
|
||||||
|
}
|
||||||
|
|
||||||
if (!RemoveQueuedPlayer(pSession) && sWorld->getIntConfig(CONFIG_INTERVAL_DISCONNECT_TOLERANCE))
|
if (!RemoveQueuedPlayer(pSession) && sWorld->getIntConfig(CONFIG_INTERVAL_DISCONNECT_TOLERANCE))
|
||||||
_disconnects[pSession->GetAccountId()] = GameTime::GetGameTime().count();
|
_disconnects[pSession->GetAccountId()] = now.count();
|
||||||
_sessions.erase(itr);
|
_sessions.erase(itr);
|
||||||
delete pSession;
|
delete pSession;
|
||||||
}
|
}
|
||||||
@@ -296,6 +333,9 @@ void WorldSessionMgr::AddSession_(WorldSession* session)
|
|||||||
if (!RemoveQueuedPlayer(oldSession) && sWorld->getIntConfig(CONFIG_INTERVAL_DISCONNECT_TOLERANCE))
|
if (!RemoveQueuedPlayer(oldSession) && sWorld->getIntConfig(CONFIG_INTERVAL_DISCONNECT_TOLERANCE))
|
||||||
_disconnects[session->GetAccountId()] = GameTime::GetGameTime().count();
|
_disconnects[session->GetAccountId()] = GameTime::GetGameTime().count();
|
||||||
|
|
||||||
|
// don't allow resetting consecutive play time on double login to same account
|
||||||
|
session->SetPreviousPlayedTime(old->second->GetConsecutivePlayTime(GameTime::GetGameTime()));
|
||||||
|
|
||||||
// pussywizard:
|
// pussywizard:
|
||||||
if (oldSession->HandleSocketClosed())
|
if (oldSession->HandleSocketClosed())
|
||||||
{
|
{
|
||||||
@@ -315,6 +355,19 @@ void WorldSessionMgr::AddSession_(WorldSession* session)
|
|||||||
delete oldSession;
|
delete oldSession;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto itr = _accountsPlayHistory.find(session->GetAccountId());
|
||||||
|
if (itr != _accountsPlayHistory.end())
|
||||||
|
{
|
||||||
|
// carry consecutive play time only if the offline gap was under the reset window
|
||||||
|
if ((GameTime::GetGameTime() - itr->second.logoutTime) < PLAY_TIME_LIMIT_FULL)
|
||||||
|
session->SetPreviousPlayedTime(itr->second.playedTime);
|
||||||
|
|
||||||
|
// entry consumed on login; erase so _accountsPlayHistory cannot grow unbounded
|
||||||
|
_accountsPlayHistory.erase(itr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_sessions[session->GetAccountId()] = session;
|
_sessions[session->GetAccountId()] = session;
|
||||||
|
|
||||||
|
|||||||
@@ -19,16 +19,24 @@
|
|||||||
#define __WORLDSESSIONMGR_H
|
#define __WORLDSESSIONMGR_H
|
||||||
|
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
|
#include "Duration.h"
|
||||||
#include "IWorld.h"
|
#include "IWorld.h"
|
||||||
#include "LockedQueue.h"
|
#include "LockedQueue.h"
|
||||||
#include "ObjectGuid.h"
|
#include "ObjectGuid.h"
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <map>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
class Player;
|
class Player;
|
||||||
class WorldPacket;
|
class WorldPacket;
|
||||||
class WorldSession;
|
class WorldSession;
|
||||||
|
|
||||||
|
struct AccountPlayHistory
|
||||||
|
{
|
||||||
|
Seconds logoutTime = Seconds::zero();
|
||||||
|
Seconds playedTime = Seconds::zero(); // reset after 5 hours offline time
|
||||||
|
};
|
||||||
|
|
||||||
class WorldSessionMgr
|
class WorldSessionMgr
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -91,6 +99,7 @@ private:
|
|||||||
|
|
||||||
SessionMap _sessions;
|
SessionMap _sessions;
|
||||||
SessionMap _offlineSessions;
|
SessionMap _offlineSessions;
|
||||||
|
std::map<uint32 /*accountId*/, AccountPlayHistory> _accountsPlayHistory;
|
||||||
|
|
||||||
typedef std::unordered_map<uint32, time_t> DisconnectMap;
|
typedef std::unordered_map<uint32, time_t> DisconnectMap;
|
||||||
DisconnectMap _disconnects;
|
DisconnectMap _disconnects;
|
||||||
@@ -103,6 +112,7 @@ private:
|
|||||||
uint32 _maxQueuedSessionCount;
|
uint32 _maxQueuedSessionCount;
|
||||||
uint32 _playerCount;
|
uint32 _playerCount;
|
||||||
uint32 _maxPlayerCount;
|
uint32 _maxPlayerCount;
|
||||||
|
uint32 _accountsPlayHistoryPruneTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define sWorldSessionMgr WorldSessionMgr::Instance()
|
#define sWorldSessionMgr WorldSessionMgr::Instance()
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ enum BillingPlanFlags
|
|||||||
SESSION_USAGE = 0x10, // Unk, NYI
|
SESSION_USAGE = 0x10, // Unk, NYI
|
||||||
SESSION_TIME_MIXTURE = 0x20, // Unk, NYI
|
SESSION_TIME_MIXTURE = 0x20, // Unk, NYI
|
||||||
SESSION_RESTRICTED = 0x40, // Unk, NYI
|
SESSION_RESTRICTED = 0x40, // Unk, NYI
|
||||||
SESSION_ENABLE_CAIS = 0x80, // Unk, NYI, possibly account play time limit related for China?
|
SESSION_ENABLE_CAIS = 0x80, // Account play time limit related for China
|
||||||
};
|
};
|
||||||
|
|
||||||
enum RealmZone
|
enum RealmZone
|
||||||
|
|||||||
@@ -359,6 +359,8 @@ void WorldConfig::BuildConfigCache()
|
|||||||
SetConfigValue<uint32>(CONFIG_TRIAL_MONEY_CAP, "Trial.MoneyCap", 100000); // copper, 10 gold
|
SetConfigValue<uint32>(CONFIG_TRIAL_MONEY_CAP, "Trial.MoneyCap", 100000); // copper, 10 gold
|
||||||
SetConfigValue<uint32>(CONFIG_TRIAL_TRADE_SKILL_CAP, "Trial.TradeSkillCap", 100);
|
SetConfigValue<uint32>(CONFIG_TRIAL_TRADE_SKILL_CAP, "Trial.TradeSkillCap", 100);
|
||||||
|
|
||||||
|
SetConfigValue<bool>(CONFIG_CAIS_ENABLED, "CAIS.Enable", false);
|
||||||
|
|
||||||
SetConfigValue<uint32>(CONFIG_EVENT_ANNOUNCE, "Event.Announce", 0);
|
SetConfigValue<uint32>(CONFIG_EVENT_ANNOUNCE, "Event.Announce", 0);
|
||||||
|
|
||||||
SetConfigValue<float>(CONFIG_CREATURE_LEASH_RADIUS, "CreatureLeashRadius", 30.0f);
|
SetConfigValue<float>(CONFIG_CREATURE_LEASH_RADIUS, "CreatureLeashRadius", 30.0f);
|
||||||
|
|||||||
@@ -518,6 +518,8 @@ enum ServerConfigs
|
|||||||
CONFIG_TRIAL_MONEY_CAP,
|
CONFIG_TRIAL_MONEY_CAP,
|
||||||
CONFIG_TRIAL_TRADE_SKILL_CAP,
|
CONFIG_TRIAL_TRADE_SKILL_CAP,
|
||||||
|
|
||||||
|
CONFIG_CAIS_ENABLED,
|
||||||
|
|
||||||
MAX_NUM_SERVER_CONFIGS
|
MAX_NUM_SERVER_CONFIGS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user