diff --git a/Source/Framework/Constants/CliDBConst.cs b/Source/Framework/Constants/CliDBConst.cs index bab7f4d5f..805429c04 100644 --- a/Source/Framework/Constants/CliDBConst.cs +++ b/Source/Framework/Constants/CliDBConst.cs @@ -991,6 +991,11 @@ namespace Framework.Constants Factional = 0x40 } + public enum ChrRacesFlag + { + AlliedRace = 0x80000 + } + public enum ChrSpecializationFlag { Caster = 0x01, diff --git a/Source/Framework/Constants/SharedConst.cs b/Source/Framework/Constants/SharedConst.cs index fa723a802..974e612e8 100644 --- a/Source/Framework/Constants/SharedConst.cs +++ b/Source/Framework/Constants/SharedConst.cs @@ -1346,6 +1346,7 @@ namespace Framework.Constants SkipCinematics, SocketTimeoutTime, SocketTimeoutTimeActive, + StartAlliedRaceLevel, StartAllExplored, StartAllRep, StartAllSpells, diff --git a/Source/Game/Chat/Commands/ResetCommands.cs b/Source/Game/Chat/Commands/ResetCommands.cs index eb35f39a4..20e1087a0 100644 --- a/Source/Game/Chat/Commands/ResetCommands.cs +++ b/Source/Game/Chat/Commands/ResetCommands.cs @@ -101,7 +101,7 @@ namespace Game.Chat byte oldLevel = (byte)target.GetLevel(); // set starting level - uint startLevel = (uint)(target.GetClass() != Class.Deathknight ? WorldConfig.GetIntValue(WorldCfg.StartPlayerLevel) : WorldConfig.GetIntValue(WorldCfg.StartDeathKnightPlayerLevel)); + uint startLevel = target.GetStartLevel(target.GetRace(), target.GetClass()); target._ApplyAllLevelScaleItemMods(false); target.SetLevel(startLevel); diff --git a/Source/Game/DataStorage/Structs/C_Records.cs b/Source/Game/DataStorage/Structs/C_Records.cs index 7367f2fa7..ed4583377 100644 --- a/Source/Game/DataStorage/Structs/C_Records.cs +++ b/Source/Game/DataStorage/Structs/C_Records.cs @@ -265,7 +265,9 @@ namespace Game.DataStorage public sbyte FemaleTextureFallbackRaceID; public sbyte FemaleTextureFallbackSex; public sbyte UnalteredVisualCustomizationRaceID; - } + + public ChrRacesFlag GetFlags() { return (ChrRacesFlag)Flags; } +} public sealed class ChrSpecializationRecord { diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index cd2c39400..8f9f06e45 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -38,6 +38,7 @@ using Game.Spells; using System; using System.Collections.Generic; using System.Linq; +using Framework.Dynamic; namespace Game.Entities { @@ -231,35 +232,7 @@ namespace Game.Entities SetInventorySlotCount(InventorySlots.DefaultSize); // set starting level - uint start_level = WorldConfig.GetUIntValue(WorldCfg.StartPlayerLevel); - if (GetClass() == Class.Deathknight) - start_level = WorldConfig.GetUIntValue(WorldCfg.StartDeathKnightPlayerLevel); - else if (GetClass() == Class.DemonHunter) - start_level = WorldConfig.GetUIntValue(WorldCfg.StartDemonHunterPlayerLevel); - - if (createInfo.TemplateSet.HasValue) - { - if (GetSession().HasPermission(RBACPermissions.UseCharacterTemplates)) - { - CharacterTemplate charTemplate = Global.CharacterTemplateDataStorage.GetCharacterTemplate(createInfo.TemplateSet.Value); - if (charTemplate != null) - { - if (charTemplate.Level > start_level) - start_level = charTemplate.Level; - } - } - else - Log.outWarn(LogFilter.Cheat, "Account: {0} (IP: {1}) tried to use a character template without given permission. Possible cheating attempt.", GetSession().GetAccountId(), GetSession().GetRemoteAddress()); - } - - if (GetSession().HasPermission(RBACPermissions.UseStartGmLevel)) - { - uint gm_level = WorldConfig.GetUIntValue(WorldCfg.StartGmLevel); - if (gm_level > start_level) - start_level = gm_level; - } - - SetLevel(start_level); + SetLevel((uint)GetStartLevel(createInfo.RaceId, createInfo.ClassId, createInfo.TemplateSet)); InitRunes(); @@ -1877,6 +1850,41 @@ namespace Game.Entities mapId = 0; return new WorldLocation(mapId, info.PositionX, info.PositionY, info.PositionZ, 0); } + + public uint GetStartLevel(Race race, Class playerClass, Optional characterTemplateId = default) + { + uint startLevel = WorldConfig.GetUIntValue(WorldCfg.StartPlayerLevel); + if (CliDB.ChrRacesStorage.LookupByKey(race).GetFlags().HasFlag(ChrRacesFlag.AlliedRace)) + startLevel = WorldConfig.GetUIntValue(WorldCfg.StartAlliedRaceLevel); + + if (playerClass == Class.Deathknight) + { + if (race == Race.PandarenAlliance || race == Race.PandarenHorde) + startLevel = Math.Max(WorldConfig.GetUIntValue(WorldCfg.StartAlliedRaceLevel), startLevel); + else + startLevel = Math.Max(WorldConfig.GetUIntValue(WorldCfg.StartDeathKnightPlayerLevel), startLevel); + } + else if (playerClass == Class.DemonHunter) + startLevel = Math.Max(WorldConfig.GetUIntValue(WorldCfg.StartDemonHunterPlayerLevel), startLevel); + + if (characterTemplateId.HasValue) + { + if (GetSession().HasPermission(RBACPermissions.UseCharacterTemplates)) + { + CharacterTemplate charTemplate = Global.CharacterTemplateDataStorage.GetCharacterTemplate(characterTemplateId.Value); + if (charTemplate != null) + startLevel = Math.Max(charTemplate.Level, startLevel); + } + else + Log.outWarn(LogFilter.Cheat, $"Account: {GetSession().GetAccountId()} (IP: {GetSession().GetRemoteAddress()}) tried to use a character template without given permission. Possible cheating attempt."); + } + + if (GetSession().HasPermission(RBACPermissions.UseStartGmLevel)) + startLevel = Math.Max(WorldConfig.GetUIntValue(WorldCfg.StartGmLevel), startLevel); + + return startLevel; + } + public override bool IsUnderWater() { return IsInWater() && diff --git a/Source/Game/Server/WorldConfig.cs b/Source/Game/Server/WorldConfig.cs index 117570025..81001a561 100644 --- a/Source/Game/Server/WorldConfig.cs +++ b/Source/Game/Server/WorldConfig.cs @@ -408,12 +408,12 @@ namespace Game Values[WorldCfg.StartPlayerLevel] = Values[WorldCfg.MaxPlayerLevel]; } - Values[WorldCfg.StartDeathKnightPlayerLevel] = GetDefaultValue("StartDeathKnightPlayerLevel", 55); + Values[WorldCfg.StartDeathKnightPlayerLevel] = GetDefaultValue("StartDeathKnightPlayerLevel", 8); if ((int)Values[WorldCfg.StartDeathKnightPlayerLevel] < 1) { - Log.outError(LogFilter.ServerLoading, "StartDeathKnightPlayerLevel ({0}) must be in range 1..MaxPlayerLevel({1}). Set to 55.", + Log.outError(LogFilter.ServerLoading, "StartDeathKnightPlayerLevel ({0}) must be in range 1..MaxPlayerLevel({1}). Set to 1.", Values[WorldCfg.StartDeathKnightPlayerLevel], Values[WorldCfg.MaxPlayerLevel]); - Values[WorldCfg.StartDeathKnightPlayerLevel] = 55; + Values[WorldCfg.StartDeathKnightPlayerLevel] = 1; } else if ((int)Values[WorldCfg.StartDeathKnightPlayerLevel] > (int)Values[WorldCfg.MaxPlayerLevel]) { @@ -422,12 +422,12 @@ namespace Game Values[WorldCfg.StartDeathKnightPlayerLevel] = Values[WorldCfg.MaxPlayerLevel]; } - Values[WorldCfg.StartDemonHunterPlayerLevel] = GetDefaultValue("StartDemonHunterPlayerLevel", 98); - if ((int)Values[WorldCfg.StartDemonHunterPlayerLevel] < 98) + Values[WorldCfg.StartDemonHunterPlayerLevel] = GetDefaultValue("StartDemonHunterPlayerLevel", 8); + if ((int)Values[WorldCfg.StartDemonHunterPlayerLevel] < 1) { - Log.outError(LogFilter.ServerLoading, "StartDemonHunterPlayerLevel ({0}) must be in range 98..MaxPlayerLevel({1}). Set to 98.", + Log.outError(LogFilter.ServerLoading, "StartDemonHunterPlayerLevel ({0}) must be in range 1..MaxPlayerLevel({1}). Set to 1.", Values[WorldCfg.StartDemonHunterPlayerLevel], Values[WorldCfg.MaxPlayerLevel]); - Values[WorldCfg.StartDemonHunterPlayerLevel] = 98; + Values[WorldCfg.StartDemonHunterPlayerLevel] = 1; } else if ((int)Values[WorldCfg.StartDemonHunterPlayerLevel] > (int)Values[WorldCfg.MaxPlayerLevel]) { @@ -436,6 +436,18 @@ namespace Game Values[WorldCfg.StartDemonHunterPlayerLevel] = Values[WorldCfg.MaxPlayerLevel]; } + Values[WorldCfg.StartAlliedRaceLevel] = GetDefaultValue("StartAlliedRacePlayerLevel", 10); + if ((int)Values[WorldCfg.StartAlliedRaceLevel] < 1) + { + Log.outError(LogFilter.ServerLoading, $"StartDemonHunterPlayerLevel ({Values[WorldCfg.StartAlliedRaceLevel]}) must be in range 1..MaxPlayerLevel({Values[WorldCfg.MaxPlayerLevel]}). Set to 1."); + Values[WorldCfg.StartAlliedRaceLevel] = 1; + } + else if ((int)Values[WorldCfg.StartAlliedRaceLevel] > (int)Values[WorldCfg.MaxPlayerLevel]) + { + Log.outError(LogFilter.ServerLoading, $"StartDemonHunterPlayerLevel ({Values[WorldCfg.StartAlliedRaceLevel]}) must be in range 1..MaxPlayerLevel({Values[WorldCfg.MaxPlayerLevel]}). Set to {Values[WorldCfg.MaxPlayerLevel]}."); + Values[WorldCfg.StartAlliedRaceLevel] = Values[WorldCfg.MaxPlayerLevel]; + } + Values[WorldCfg.StartPlayerMoney] = GetDefaultValue("StartPlayerMoney", 0); if ((int)Values[WorldCfg.StartPlayerMoney] < 0) { diff --git a/Source/WorldServer/WorldServer.conf.dist b/Source/WorldServer/WorldServer.conf.dist index ded6bed22..2cab41979 100644 --- a/Source/WorldServer/WorldServer.conf.dist +++ b/Source/WorldServer/WorldServer.conf.dist @@ -843,9 +843,9 @@ StartPlayerLevel = 1 # StartDeathKnightPlayerLevel # Description: Starting level for death knights after creation. # Range: 1-MaxPlayerLevel -# Default: 10 +# Default: 8 -StartDeathKnightPlayerLevel = 10 +StartDeathKnightPlayerLevel = 8 # # StartDemonHunterPlayerLevel @@ -855,6 +855,14 @@ StartDeathKnightPlayerLevel = 10 StartDemonHunterPlayerLevel = 8 +# +# StartAlliedRacePlayerLevel +# Description: Staring level for allied races after creation. +# Range: 1-MaxPlayerLevel +# Default: 10 + +StartAlliedRacePlayerLevel = 10 + # # StartPlayerMoney # Description: Amount of money (in Copper) that a character has after creation.