Implemented Basic Creature Scaling

Trainer followup
Misc Fixes
This commit is contained in:
hondacrx
2017-07-31 14:18:07 -04:00
parent 890ce8d81a
commit 473cea0f06
26 changed files with 370 additions and 96 deletions
+103 -17
View File
@@ -288,8 +288,8 @@ namespace Game.Entities
if (updateLevel)
SelectLevel();
UpdateLevelDependantStats();
else
UpdateLevelDependantStats(); // We still re-initialize level dependant stats on entry update
SetMeleeDamageSchool((SpellSchools)cInfo.DmgSchool);
SetModifierValue(UnitMods.ResistanceHoly, UnitModifierType.BaseValue, cInfo.Resistance[(int)SpellSchools.Holy]);
@@ -1090,11 +1090,23 @@ namespace Game.Entities
{
CreatureTemplate cInfo = GetCreatureTemplate();
// level
byte minlevel = (byte)Math.Min(cInfo.Maxlevel, cInfo.Minlevel);
byte maxlevel = (byte)Math.Max(cInfo.Maxlevel, cInfo.Minlevel);
byte level = (byte)(minlevel == maxlevel ? minlevel : RandomHelper.URand(minlevel, maxlevel));
SetLevel(level);
if (!HasScalableLevels())
{
// level
byte minlevel = (byte)Math.Min(cInfo.Maxlevel, cInfo.Minlevel);
byte maxlevel = (byte)Math.Max(cInfo.Maxlevel, cInfo.Minlevel);
byte level = (byte)(minlevel == maxlevel ? minlevel : RandomHelper.URand(minlevel, maxlevel));
SetLevel(level);
}
else
{
SetLevel(cInfo.levelScaling.Value.MaxLevel);
SetUInt32Value(UnitFields.ScalingLevelMin, cInfo.levelScaling.Value.MinLevel);
SetUInt32Value(UnitFields.ScalingLevelMax, cInfo.levelScaling.Value.MaxLevel);
SetUInt32Value(UnitFields.ScalingLevelDelta, (uint)cInfo.levelScaling.Value.DeltaLevel);
}
UpdateLevelDependantStats();
}
void UpdateLevelDependantStats()
@@ -1663,8 +1675,8 @@ namespace Game.Entities
if (m_originalEntry != GetEntry())
UpdateEntry(m_originalEntry);
SelectLevel();
else
SelectLevel();
setDeathState(DeathState.JustRespawned);
@@ -2344,17 +2356,91 @@ namespace Game.Entities
m_respawnTime = m_corpseRemoveTime + m_respawnDelay;
}
public bool HasScalableLevels()
{
CreatureTemplate cinfo = GetCreatureTemplate();
return cinfo.levelScaling.HasValue;
}
ulong GetMaxHealthByLevel(uint level)
{
CreatureTemplate cInfo = GetCreatureTemplate();
CreatureBaseStats stats = Global.ObjectMgr.GetCreatureBaseStats(level, cInfo.UnitClass);
return stats.GenerateHealth(cInfo);
}
float GetHealthMultiplierForTarget(WorldObject target)
{
if (!HasScalableLevels())
return 1.0f;
uint levelForTarget = GetLevelForTarget(target);
if (getLevel() < levelForTarget)
return 1.0f;
return GetMaxHealthByLevel(levelForTarget) / GetCreateHealth();
}
float GetBaseDamageForLevel(uint level)
{
CreatureTemplate cInfo = GetCreatureTemplate();
CreatureBaseStats stats = Global.ObjectMgr.GetCreatureBaseStats(level, cInfo.UnitClass);
return stats.GenerateBaseDamage(cInfo);
}
float GetDamageMultiplierForTarget(WorldObject target)
{
if (!HasScalableLevels())
return 1.0f;
uint levelForTarget = GetLevelForTarget(target);
return GetBaseDamageForLevel(levelForTarget) / GetBaseDamageForLevel(getLevel());
}
float GetBaseArmorForLevel(uint level)
{
CreatureTemplate cInfo = GetCreatureTemplate();
CreatureBaseStats stats = Global.ObjectMgr.GetCreatureBaseStats(level, cInfo.UnitClass);
return stats.GenerateArmor(cInfo);
}
float GetArmorMultiplierForTarget(WorldObject target)
{
if (!HasScalableLevels())
return 1.0f;
uint levelForTarget = GetLevelForTarget(target);
return GetBaseArmorForLevel(levelForTarget) / GetBaseArmorForLevel(getLevel());
}
public override uint GetLevelForTarget(WorldObject target)
{
if (!isWorldBoss() || !target.IsTypeId(TypeId.Unit))
return base.GetLevelForTarget(target);
Unit unitTarget = target.ToUnit();
if (unitTarget)
{
if (isWorldBoss())
{
int level = (int)(unitTarget.getLevel() + WorldConfig.GetIntValue(WorldCfg.WorldBossLevelDiff));
return (uint)MathFunctions.RoundToInterval(ref level, 1u, 255u);
}
uint level = target.ToUnit().getLevel() + WorldConfig.GetUIntValue(WorldCfg.WorldBossLevelDiff);
if (level < 1)
return 1;
if (level > 255)
return 255;
return level;
// If this creature should scale level, adapt level depending of target level
// between UNIT_FIELD_SCALING_LEVEL_MIN and UNIT_FIELD_SCALING_LEVEL_MAX
if (HasScalableLevels())
{
uint targetLevelWithDelta = (uint)(unitTarget.getLevel() + GetCreatureTemplate().levelScaling.Value.DeltaLevel);
if (target.IsPlayer())
targetLevelWithDelta += target.GetUInt32Value(PlayerFields.ScalingLevelDelta);
return MathFunctions.RoundToInterval(ref targetLevelWithDelta, GetUInt32Value(UnitFields.ScalingLevelMin), GetUInt32Value(UnitFields.ScalingLevelMax));
}
}
return base.GetLevelForTarget(target);
}
public string GetAIName()
+9
View File
@@ -19,6 +19,7 @@ using Framework.Collections;
using Framework.Constants;
using System;
using System.Collections.Generic;
using Framework.Dynamic;
namespace Game.Entities
{
@@ -37,6 +38,7 @@ namespace Game.Entities
public string IconName;
public uint GossipMenuId;
public short Minlevel;
public Optional<CreatureLevelScaling> levelScaling;
public short Maxlevel;
public int HealthScalingExpansion;
public uint RequiredExpansion;
@@ -384,4 +386,11 @@ namespace Game.Entities
m_items.Clear();
}
}
public struct CreatureLevelScaling
{
public ushort MinLevel;
public ushort MaxLevel;
public short DeltaLevel;
}
}
+10 -3
View File
@@ -18,7 +18,8 @@ namespace Game.Entities
public Array<uint> ReqAbility = new Array<uint>(3);
public byte ReqLevel;
public uint CastSpellId;
public uint LearnedSpellId;
public bool IsCastable() { return LearnedSpellId != SpellId; }
}
public class Trainer
@@ -84,8 +85,8 @@ namespace Game.Entities
player.SendPlaySpellVisualKit(362, 1, 0); // 113 EmoteSalute
// learn explicitly or cast explicitly
if (trainerSpell.CastSpellId != 0)
player.CastSpell(player, trainerSpell.CastSpellId, true);
if (trainerSpell.IsCastable())
player.CastSpell(player, trainerSpell.SpellId, true);
else
player.LearnSpell(trainerSpell.SpellId, false);
}
@@ -129,6 +130,12 @@ namespace Game.Entities
if (player.getLevel() < trainerSpell.ReqLevel)
return TrainerSpellState.Unavailable;
// check ranks
uint previousRankSpellId = Global.SpellMgr.GetPrevSpellInChain(trainerSpell.LearnedSpellId);
if (previousRankSpellId != 0)
if (!player.HasSpell(previousRankSpellId))
return TrainerSpellState.Unavailable;
// check additional spell requirement
foreach (var spellId in Global.SpellMgr.GetSpellsRequiredForSpellBounds(trainerSpell.SpellId))
if (!player.HasSpell(spellId))
+19 -10
View File
@@ -850,7 +850,7 @@ namespace Game.Entities
DynamicUpdateMask arrayMask = new DynamicUpdateMask((uint)values.Length);
arrayMask.EncodeDynamicFieldChangeType(_dynamicChangesMask[index], updateType);
if (updateType == UpdateType.Values && _dynamicChangesMask[index] == DynamicFieldChangeType.ValueAndSizeChanged)
arrayMask.SetCount(values.Length);
arrayMask.SetCount(values.Length);
for (var v = 0; v < values.Length; ++v)
{
@@ -2029,7 +2029,7 @@ namespace Game.Entities
Map map = GetMap();
GameObject go = new GameObject();
if (!go.Create(entry, map, GetPhaseMask(), pos, rotation, 255, GameObjectState.Ready))
if (!go.Create(entry, map, GetPhaseMask(), pos, rotation, 255, GameObjectState.Ready))
return null;
go.CopyPhaseFrom(this);
@@ -2498,14 +2498,23 @@ namespace Game.Entities
public void SetFieldNotifyFlag(uint flag) { _fieldNotifyFlags |= flag; }
public void RemoveFieldNotifyFlag(uint flag) { _fieldNotifyFlags &= ~flag; }
public Creature ToCreature() { return IsTypeId(TypeId.Unit) ? (this as Creature) : null; }
public Player ToPlayer() { return IsTypeId(TypeId.Player) ? (this as Player) : null; }
public GameObject ToGameObject() { return IsTypeId(TypeId.GameObject) ? (this as GameObject) : null; }
public Unit ToUnit() { return IsTypeId(TypeId.Unit) || IsTypeId(TypeId.Player) ? (this as Unit) : null; }
public Corpse ToCorpse() { return IsTypeId(TypeId.Corpse) ? (this as Corpse) : null; }
public DynamicObject ToDynamicObject() { return IsTypeId(TypeId.DynamicObject) ? (this as DynamicObject) : null; }
public AreaTrigger ToAreaTrigger() { return IsTypeId(TypeId.AreaTrigger) ? (this as AreaTrigger) : null; }
public Conversation ToConversation() { return IsTypeId(TypeId.Conversation) ? (this as Conversation) : null; }
bool IsCreature() { return GetTypeId() == TypeId.Unit; }
public bool IsPlayer() { return GetTypeId() == TypeId.Player; }
bool IsGameObject() { return GetTypeId() == TypeId.GameObject; }
bool IsUnit() { return isTypeMask(TypeMask.Unit); }
bool IsCorpse() { return GetTypeId() == TypeId.Corpse; }
bool IsDynObject() { return GetTypeId() == TypeId.DynamicObject; }
bool IsAreaTrigger() { return GetTypeId() == TypeId.AreaTrigger; }
bool IsConversation() { return GetTypeId() == TypeId.Conversation; }
public Creature ToCreature() { return IsCreature() ? (this as Creature) : null; }
public Player ToPlayer() { return IsPlayer() ? (this as Player) : null; }
public GameObject ToGameObject() { return IsGameObject() ? (this as GameObject) : null; }
public Unit ToUnit() { return IsUnit() ? (this as Unit) : null; }
public Corpse ToCorpse() { return IsCorpse() ? (this as Corpse) : null; }
public DynamicObject ToDynamicObject() { return IsDynObject() ? (this as DynamicObject) : null; }
public AreaTrigger ToAreaTrigger() { return IsAreaTrigger() ? (this as AreaTrigger) : null; }
public Conversation ToConversation() { return IsConversation() ? (this as Conversation) : null; }
public virtual void Update(uint diff) { }
+1 -1
View File
@@ -117,7 +117,7 @@ namespace Game.Entities
// 2.4. _maxNotGrayMember - maximum level of alive group member within reward distance,
// for whom victim is not gray;
uint grayLevel = Formulas.GetGrayLevel(lvl);
if (_victim.getLevel() > grayLevel && (!_maxNotGrayMember || _maxNotGrayMember.getLevel() < lvl))
if (_victim.GetLevelForTarget(member) > grayLevel && (!_maxNotGrayMember || _maxNotGrayMember.getLevel() < lvl))
_maxNotGrayMember = member;
}
}
+1 -1
View File
@@ -2363,7 +2363,7 @@ namespace Game.Entities
}
SetUInt32Value(UnitFields.Level, result.Read<uint>(6));
SetUInt32Value(PlayerFields.Xp, result.Read<uint>(7));
SetXP(result.Read<uint>(7));
_LoadIntoDataField(result.Read<string>(65), (int)PlayerFields.ExploredZones1, PlayerConst.ExploredZonesSize);
_LoadIntoDataField(result.Read<string>(66), (int)PlayerFields.KnownTitles, PlayerConst.KnowTitlesSize * 2);
+1 -1
View File
@@ -104,7 +104,7 @@ namespace Game.Entities
byte k_level = (byte)getLevel();
byte k_grey = (byte)Formulas.GetGrayLevel(k_level);
byte v_level = (byte)victim.getLevel();
byte v_level = (byte)victim.GetLevelForTarget(this);
if (v_level <= k_grey)
return false;
+23 -7
View File
@@ -1646,7 +1646,7 @@ namespace Game.Entities
if (Rep.RepFaction1 != 0 && (!Rep.TeamDependent || team == Team.Alliance))
{
int donerep1 = CalculateReputationGain(ReputationSource.Kill, victim.getLevel(), Rep.RepValue1, (int)(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction1));
int donerep1 = CalculateReputationGain(ReputationSource.Kill, victim.GetLevelForTarget(this), Rep.RepValue1, (int)(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction1));
donerep1 = (int)(donerep1 * rate);
FactionRecord factionEntry1 = CliDB.FactionStorage.LookupByKey(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction1);
@@ -1657,7 +1657,7 @@ namespace Game.Entities
if (Rep.RepFaction2 != 0 && (!Rep.TeamDependent || team == Team.Horde))
{
int donerep2 = CalculateReputationGain(ReputationSource.Kill, victim.getLevel(), Rep.RepValue2, (int)(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction2));
int donerep2 = CalculateReputationGain(ReputationSource.Kill, victim.GetLevelForTarget(this), Rep.RepValue2, (int)(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction2));
donerep2 = (int)(donerep2 * rate);
FactionRecord factionEntry2 = CliDB.FactionStorage.LookupByKey(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction2);
@@ -4116,9 +4116,12 @@ namespace Game.Entities
if (!IsSectionFlagValid(hair, class_, create))
return false;
CharSectionsRecord facialHair = Global.DB2Mgr.GetCharSectionEntry(race, CharSectionType.Hair, gender, facialHairId, hairColor);
if (facialHair == null)
return false;
if (facialHairId != 0)
{
CharSectionsRecord facialHair = Global.DB2Mgr.GetCharSectionEntry(race, CharSectionType.FacialHair, gender, facialHairId, hairColor);
if (facialHair == null)
return false;
}
for (int i = 0; i < PlayerConst.CustomDisplaySize; ++i)
{
@@ -5064,7 +5067,7 @@ namespace Game.Entities
// Used in triggers for check "Only to targets that grant experience or honor" req
public bool isHonorOrXPTarget(Unit victim)
{
uint v_level = victim.getLevel();
uint v_level = victim.GetLevelForTarget(this);
uint k_grey = Formulas.GetGrayLevel(getLevel());
// Victim level less gray level
@@ -6412,6 +6415,19 @@ namespace Game.Entities
SendMovementSetCollisionHeight(scale * GetCollisionHeight(IsMounted()));
}
void SetXP(uint xp)
{
SetUInt32Value(PlayerFields.Xp, xp);
int playerLevelDelta = 0;
// If XP < 50%, player should see scaling creature with -1 level except for level max
if (getLevel() < SharedConst.MaxLevel && xp < (GetUInt32Value(PlayerFields.NextLevelXp) / 2))
playerLevelDelta = -1;
SetInt32Value(PlayerFields.ScalingLevelDelta, playerLevelDelta);
}
public void GiveXP(uint xp, Unit victim, float group_rate = 1.0f)
{
if (xp < 1)
@@ -6467,7 +6483,7 @@ namespace Game.Entities
nextLvlXP = GetUInt32Value(PlayerFields.NextLevelXp);
}
SetUInt32Value(PlayerFields.Xp, newXP);
SetXP(newXP);
}
public void HandleBaseModValue(BaseModGroup modGroup, BaseModType modType, float amount, bool apply)
+4
View File
@@ -420,6 +420,10 @@ namespace Game.Entities
public ulong CountPctFromMaxHealth(int pct) { return MathFunctions.CalculatePct(GetMaxHealth(), pct); }
ulong CountPctFromCurHealth(int pct) { return MathFunctions.CalculatePct(GetHealth(), pct); }
public virtual float GetHealthMultiplierForTarget(WorldObject target) { return 1.0f; }
public virtual float GetDamageMultiplierForTarget(WorldObject target) { return 1.0f; }
public virtual float GetArmorMultiplierForTarget(WorldObject target) { return 1.0f; }
//Powers
public PowerType getPowerType()
{
+17 -6
View File
@@ -724,7 +724,10 @@ namespace Game.Entities
{
absorb += damage;
damage = 0;
return;
}
damage *= (uint)GetDamageMultiplierForTarget(victim);
}
void DealMeleeDamage(CalcDamageInfo damageInfo, bool durabilityLoss)
{
@@ -787,7 +790,7 @@ namespace Game.Entities
// there is a newbie protection, at level 10 just 7% base chance; assuming linear function
if (victim.getLevel() < 30)
Probability = 0.65f * victim.getLevel() + 0.5f;
Probability = 0.65f * victim.GetLevelForTarget(this) + 0.5f;
uint VictimDefense = victim.GetMaxSkillValueForLevel(this);
uint AttackerMeleeSkill = GetMaxSkillValueForLevel();
@@ -1018,6 +1021,8 @@ namespace Game.Entities
victim.ToCreature().LowerPlayerDamageReq(health < damage ? health : damage);
}
damage /= (uint)victim.GetHealthMultiplierForTarget(this);
if (health <= damage)
{
Log.outDebug(LogFilter.Unit, "DealDamage: victim just died");
@@ -1221,6 +1226,10 @@ namespace Game.Entities
packet.BlockAmount = (int)damageInfo.blocked_amount;
packet.LogData.Initialize(damageInfo.attacker);
SandboxScalingData sandboxScalingData = new SandboxScalingData();
if (sandboxScalingData.GenerateDataForUnits(damageInfo.attacker, damageInfo.target))
packet.SandboxScaling = sandboxScalingData;
SendCombatLogMessage(packet);
}
public void CombatStart(Unit target, bool initialAggro = true)
@@ -2289,7 +2298,7 @@ namespace Game.Entities
byte bossLevel = 83;
uint bossResistanceConstant = 510;
uint resistanceConstant = 0;
uint level = victim.getLevel();
uint level = victim.GetLevelForTarget(this);
if (level == bossLevel)
resistanceConstant = bossResistanceConstant;
@@ -2626,6 +2635,8 @@ namespace Game.Entities
{
float armor = victim.GetArmor();
armor *= victim.GetArmorMultiplierForTarget(attacker);
// bypass enemy armor by SPELL_AURA_BYPASS_ARMOR_FOR_CASTER
int armorBypassPct = 0;
var reductionAuras = victim.GetAuraEffectsByType(AuraType.BypassArmorForCaster);
@@ -2655,10 +2666,10 @@ namespace Game.Entities
if (IsTypeId(TypeId.Player))
{
float maxArmorPen = 0;
if (victim.getLevel() < 60)
maxArmorPen = 400 + 85 * victim.getLevel();
if (victim.GetLevelForTarget(attacker) < 60)
maxArmorPen = 400 + 85 * victim.GetLevelForTarget(attacker);
else
maxArmorPen = 400 + 85 * victim.getLevel() + 4.5f * 85 * (victim.getLevel() - 59);
maxArmorPen = 400 + 85 * victim.GetLevelForTarget(attacker) + 4.5f * 85 * (victim.GetLevelForTarget(attacker) - 59);
// Cap armor penetration to this number
maxArmorPen = Math.Min((armor + maxArmorPen) / 3, armor);
@@ -2671,7 +2682,7 @@ namespace Game.Entities
if (MathFunctions.fuzzyLe(armor, 0.0f))
return damage;
uint attackerLevel = attacker.getLevel();
uint attackerLevel = attacker.GetLevelForTarget(victim);
if (attackerLevel > CliDB.ArmorMitigationByLvlGameTable.GetTableRowCount())
attackerLevel = (uint)CliDB.ArmorMitigationByLvlGameTable.GetTableRowCount();
+1 -1
View File
@@ -745,7 +745,7 @@ namespace Game.Entities
if (!pet.CreateBaseAtCreature(creatureTarget))
return null;
uint level = creatureTarget.getLevel() + 5 < getLevel() ? (getLevel() - 5) : creatureTarget.getLevel();
uint level = creatureTarget.GetLevelForTarget(this) + 5 < getLevel() ? (getLevel() - 5) : creatureTarget.GetLevelForTarget(this);
InitTamedPet(pet, level, spell_id);
+11
View File
@@ -3079,6 +3079,11 @@ namespace Game.Entities
packet.Absorbed = (int)log.absorb;
packet.Periodic = log.periodicLog;
packet.Flags = (int)log.HitInfo;
SandboxScalingData sandboxScalingData = new SandboxScalingData();
if (sandboxScalingData.GenerateDataForUnits(log.attacker, log.target))
packet.SandboxScaling.Set(sandboxScalingData);
SendCombatLogMessage(packet);
}
@@ -3102,6 +3107,12 @@ namespace Game.Entities
spellLogEffect.Crit = info.critical;
/// @todo: implement debug info
SandboxScalingData sandboxScalingData = new SandboxScalingData();
Unit caster = Global.ObjAccessor.GetUnit(this, aura.GetCasterGUID());
if (caster)
if (sandboxScalingData.GenerateDataForUnits(caster, this))
spellLogEffect.SandboxScaling.Set(sandboxScalingData);
data.Effects.Add(spellLogEffect);
SendCombatLogMessage(data);
+1
View File
@@ -2127,6 +2127,7 @@ namespace Game.Entities
{
return GetUInt32Value(UnitFields.Level);
}
public override uint GetLevelForTarget(WorldObject target)
{
return getLevel();